Commit Graph

188 Commits

Author SHA1 Message Date
kami 02e963d6cc feat(tui-go): idea board viewer
OverlayIdeas (opened with I, global/cross-session) lists the idea board in the
artifact-viewer shell: ↑↓ to move, x/d to remove (sends DiscardIdea, optimistic
drop), esc to close. Pull-based via ListIdeas -> idea.list.
2026-06-14 14:36:13 +04:00
kami 04e931c860 feat(server): idea board list + discard
ListIdeas -> idea.list (StreamQueries folds the board from the log, replay-neutral)
and DiscardIdea -> append IdeaDiscardedEvent landed in the capturing session, then
reply with the refreshed board. IdeaDto on the wire.
2026-06-14 14:33:11 +04:00
kami b563d9841d feat(tui-go): workflow-propose choice panel
Render a workflow.proposed frame as a single-select picker with a manual-answer
slot: ↑↓/jk to choose, enter to launch, e for custom, esc to peek away. Picking
a candidate sends StartSession (and focuses the launched session); the custom
slot continues the conversation via ChatInput. Reuses the modal helpers from the
clarification view.
2026-06-14 13:29:34 +04:00
kami b62b6e09a1 feat(server): workflow.proposed WS frame
Map WorkflowProposedEvent to a workflow.proposed ServerMessage carrying the
candidates + original request. No new ClientMessage: a pick reuses StartSession
(launch the chosen workflow seeded with the request) and the manual-answer slot
reuses ChatInput.
2026-06-14 13:29:01 +04:00
kami 218a98034e feat(tui-go): interactive clarification question view
Render stage clarification questions as an AskUserQuestion-style form:
header chip, prompt, selectable option chips, and a free-text custom slot.
Arrow/hjkl nav, space to select, e for custom, enter to submit. Submit
guards on all-answered and sends ClarificationResponse; the parked stage
re-runs with the answers in context.
2026-06-14 13:00:59 +04:00
kami 6242b590e4 feat(server): clarification WS protocol + wiring
Surfaces the clarification loop over the wire: ServerMessage.ClarificationRequired
(clarification.required, carrying the structured questions) is mapped from
ClarificationRequestedEvent; ClientMessage.ClarificationResponse carries the
operator's answers and is routed to orchestrator.submitClarification on both
the global and session streams. Reuses the core ClarificationQuestion/Answer
types as the wire shape. Round-trip tests for both directions.
2026-06-14 03:35:59 +04:00
kami 7a0d4d0ee2 feat(research): wire tools + research workflow graph (research-workflow §2/§3)
Makes the research feature runnable end-to-end, off by default.

- config: [tools.research] (enabled, searxng_url, max_results, max_fetch_bytes).
- registration: web_search/web_fetch are built into BOTH the default and per-workspace
  tool registries when research.enabled, sharing one HTTP client threaded from Main
  (none built on the static path). Egress stays harness-enforced: web_fetch is T2
  (operator-approved) and the existing NetworkHostRule still applies.
- workflow: examples/workflows/research.toml — decompose → gather → report, with the
  three artifact schemas and prompts. Fan-out (search per sub-question, fetch per source)
  runs as repeated tool calls inside the gather stage (Correx has no parallel agents);
  per-source synthesis into the dossier is the compression step, so the report stage
  consumes summaries, never raw pages. ResearchWorkflowTest validates the graph contract.

To run: set [tools.research].enabled, register the 3 [[artifacts]], copy research.toml +
prompts + schemas into the workflows dir, start SearXNG. Launch like any workflow (the
T2 fetch approval surfaces as an approval card; the report opens in the artifact viewer).

Follow-ups (noted, not blocking): batch fetch-approval at the source-list level (§3),
a dedicated SourceFetched/LowQualityExtraction event (quality + content hash are already
in tool-result metadata), dynamic per-session egress allowlist, and the web approval client (§6).
2026-06-13 23:18:19 +04:00
kami f8fd2601a8 feat(server,cli): event-sourced health checks — disk watermark probe (observability §4)
Health states are events. A HealthMonitor polls registered HealthProbes on an
interval and appends HealthDegraded/HealthRestored only on a status transition
(hysteresis), so health is observable, replayable, and renderable like every
other fact in the log. The probe reads nondeterministic environment state and
records it at observe-time (Invariant #9); HealthProjection rebuilds current
health by replay, never re-probing (Invariant #8). Monitor is live-only, started
in ServerModule.start alongside narration; seeded from the projection so a
restart does not re-emit a degraded the log already records.

First concrete probe: DiskWatermarkProbe — event-log + CAS size and growth rate,
warning before the 2am surprise. Read surface mirrors `correx stats`:
GET /health/checks replays the system session and `correx health` renders it.
Health events ride the reserved __system__ session, filtered from operator
session listings.

LLAMA_SERVER (liveness + tokens/sec trend) and EVENT_STORE (append/fsync latency)
probes plug into the same HealthProbe framework as follow-ups.
2026-06-13 22:19:33 +04:00
kami 4e5e4e56ea feat(toolintent): stage write-manifest enforcement (role-reliability §2)
Scope creep — an implementer writing files it never declared — is the
dominant local-model failure that compiler/tests don't catch. A stage can
now declare a `writes` manifest (workspace-relative globs); a FILE_WRITE
that resolves inside the workspace but outside the declared set is raised
as PATH_OUTSIDE_MANIFEST → BLOCK, so the model gets the violation as tool
feedback and retries within scope (the §2 bounded-retry signal).

Implemented as a plane-2 rule beside PathContainmentRule (same effect-based
dispatch on FILE_WRITE, same symlink-safe WorldProbe resolution, same
recorded observations so replay reads them back — invariant #9). An empty
manifest is unrestricted; out-of-workspace targets stay PathContainmentRule's
concern, so the two rules don't double-flag.

- StageConfig.writeManifest + TomlWorkflowLoader `writes` parsing
- ToolCallAssessmentInput.writeManifest, threaded from the active stage via
  SessionOrchestrator.runPlane2Assessment
- new ManifestContainmentRule, registered in the server assessor
- shared candidatePathStrings extracted so both path rules judge the same
  target set
- role_pipeline.toml documents the option on the implementer stage

The dedicated ManifestViolationEvent the spec names is not added: the
violation is already recorded replayably in ToolCallAssessedEvent (issue +
observations + BLOCK) and surfaced in the TUI rationale band. A first-class
event is worth adding only once reconciliation consumes it.
2026-06-13 16:05:39 +04:00
kami 65df3f4fad feat(tui,kernel): deterministic command-approval parse layer (§5.1)
Layer 1 of tui-requirements §5: a no-LLM analyzer that shell-lexes a
proposed command, classifies binaries against a known-command table, and
mechanically flags the constructs that widen blast radius — sudo/doas,
recursive delete, pipe-to-shell (skipping wrappers so `| sudo sh` counts),
redirects outside the workspace, network binaries, base64/eval, and
unparseable input (itself a red flag). Pure + replay-stable: same preview
+ workspaceRoot → same analysis, no environment reads.

The approval band renders command approvals as a card — worst-first flag
chips over the reconstructed command line, each token colored by severity
(T0–T4 ramp). ^x opens a scrollable fullscreen view so a long command is
never truncated. Diff and plain-preview paths are unchanged.

Server: computeToolPreview now renders the shell tool's argv as a full,
shell-quoted command line instead of the 200-char JSON-args fallback,
honouring §3's "full untruncated command". The TUI parser accepts both
the rendered line and the older `{"argv":[...]}` shape for replay.

Layer 2 (model annotation) deliberately deferred per the spec.
2026-06-13 15:18:27 +04:00
kami b4ca17c85f fix(tui): responsive layout for slim terminals
The TUI overflowed and became unreadable on narrow terminals: the status bar and
footer spilled far past the edge (justify never truncated), the two-column main
split shrank the events panel to ~20 cols, and the stats overlay wrapped mid-word.

- justify is now width-safe: shrinks the left segment first (the right holds the
  high-signal connection clock / active spinner), clamps the right only as a last
  resort, ANSI-aware so it never slices escape codes. No line can exceed the width.
- Main area collapses below narrowWidth (96): single full-width session list when
  idle; output stacked over the live event strip in-session (the strip is too
  valuable to drop — the constraint is width, not height).
- Compact chrome when slim: status drops workspace/gauge/provider-suffix and uses a
  short clock ("◷ 3s") + bare spinner; footer uses a reduced hint set and drops the
  "Soft·blue" badge; approval action row tightens its gaps so decision keys stay
  visible.
- Overlays get near-full-width on slim terminals; stats pane uses compact formats +
  per-line clipping so it never wraps.

Tests: no rendered line exceeds the terminal width at 40–160 cols (incl. the stats
overlay); narrow in-session main stacks output+events.
2026-06-13 11:01:16 +04:00
kami 8b896b519a feat(tui): truthful-state §2 — last-event clock + unknown-event raw fallback
Implements the load-bearing TUI-requirements §2 hard requirements:

- Last-event clock: "last event Ns ago" always visible in the status bar while
  in-session, updating every frame tick. Turns warn-colored when an active session
  has gone quiet past the stale threshold — the "thinking vs hung" tell. The
  WebSocket connection indicator (●/◌/○) already covers immediate drop visibility.
- Unknown-event raw fallback: applyServer now surfaces any unrecognized,
  session-scoped event type as a raw row in the event stream instead of silently
  dropping it (the frontend must not lie by omission). Recognized-but-unrendered
  types (router.response, protocol_error) get an explicit no-op so the default
  branch means genuinely unknown.

Tests: agoLabel formatting, unknown-event surfaces-as-row, known-ignored
no-spurious-row. Preview "session" frame exercises the clock.

Deferred: the full Go↔Kotlin per-event-type render matrix (§2 last bullet) — a
larger fixture effort than the core behaviors landed here.
2026-06-13 10:46:06 +04:00
kami 4f6bfa85f7 feat(server,tui): session stats pane — metrics over the WS bus + Bubble Tea overlay
Surfaces the observability metrics (observability-spec §3-tier-2) in the Go TUI:
a session-summary pane showing duration, token throughput per provider, tool
time, approval latency per tier, failure counts, and the session wall-time
accounting split. Press `S` (or palette → "session stats").

Mirrors the artifacts/config fetch pattern exactly — a WS request/response, not a
new transport. Server-side reuses the already-tested MetricsInspectionService
(one MetricsReport definition, two wires: REST `correx stats` + WS).

Server:
- ClientMessage.GetSessionStats + ServerMessage.SessionStats(@SerialName
  session.stats), reusing MetricsReport as the nested payload.
- StreamQueries.sessionStats replays via MetricsInspectionService (pure read,
  replay-neutral); routed in GlobalStreamHandler.
- ServerMessageSerializationTest golden pins the session.stats wire format.

TUI (Go/Bubble Tea):
- protocol: TypeSessionStats + StatsDto/nested structs + GetSessionStats encoder
  + golden decode test (cross-language contract).
- OverlayStats pane (statsModal), `S` key + palette entry + footer hint,
  loading/cache-by-session state, bounded breakdown rows.
- demo `stats` preview case for serverless visual verification.
2026-06-13 10:39:52 +04:00
kami fe941408a7 feat(server,cli): observability metrics as a replayable projection + correx stats
Implements observability-spec §2 + §3-tier-1: metrics as a pure fold over the
event log, no third pillar, no OTel. Every metric is replayable over the full
historical log because it derives from events other contexts already emit — no
new EventPayload subclasses, so no serialization registry change.

- MetricsProjection: Projection<MetricsState> folding inference/tool/approval/
  stage/workflow events into raw sums; approval request→decision latency is
  correlated in-fold via a transient pending-request map.
- MetricsInspectionService: rebuilds via DefaultEventReplayer, derives read-side
  ratios (token throughput, avg approval wait, session wall-time accounting split
  into inference / tool / approval-wait / other) into MetricsReport.
- GET /sessions/{id}/metrics route, mirroring the replay endpoint.
- `correx stats <sessionId>` CLI client (--json passthrough), mirroring replay.
- Tests: seeded-event fold correctness with controlled timestamps (7) + CLI
  render smoke tests (4).
2026-06-13 10:23:40 +04:00
kami 75703ec0c8 refactor(server,cli): shared payload-discriminator helper, single-read digest reuse, DOT label escaping 2026-06-12 19:23:05 +04:00
kami 4422aa8b7a feat(cli): causation-graph DOT export for event inspector 2026-06-12 19:17:26 +04:00
kami 64b58e3b05 feat(server,cli): session replay inspection — timeline + determinism digest 2026-06-12 14:39:54 +04:00
kami a04cd1fa24 feat(server,cli): event stream inspector — /sessions/{id}/events + correx events 2026-06-12 14:28:19 +04:00
kami a7d5211f3f feat(server,logging): correlation-structured MDC logging at seams
Implement T1 of observability epic: every server log line carries sessionId
(and stageId where known) via SLF4J MDC, propagated across coroutine
suspension points.

Changes:
- Create com.correx.apps.server.logging.Correlation: withSessionContext()
  helper that sets MDC (sessionId, stageId), wraps block in MDCContext(),
  and restores prior values on exit; supports safe nesting
- Wrap launchSessionRun() orchestrator loop in withSessionContext(sessionId)
  so all session logs carry the sessionId
- Wrap GlobalStreamHandler ChatInput and StartChatSession onUserInput call
  sites in withSessionContext(sessionId)
- Wrap SessionStreamHandler ChatInput onUserInput in withSessionContext(sessionId)
- Update log4j2.properties patterns: append
  %notEmpty{ [%X{sessionId}]}%notEmpty{ [%X{stageId}]} before %msg
  on both console and file appenders
- Add kotlinx-coroutines-slf4j:1.9.0 dependency for MDCContext

Test (6 tests):
- withSessionContext sets/restores sessionId ✓
- withSessionContext sets/restores stageId ✓
- Nested contexts stack and restore correctly ✓
- MDC cleared when stageId not provided ✓
- Propagation across coroutine boundaries via MDCContext ✓
- Cleanup after block exit ✓

Acceptance:
- Zero behavior change; no core module touched ✓
- All imports verified as used ✓
- No bare try-catch; try/finally used correctly ✓
- Detekt-compliant ✓
- Full test suite passes (120 tests, 119 passed, 1 skipped) ✓
2026-06-12 14:17:54 +04:00
kami e195c79510 fix(server): record boot-time parking of stale sessions as OrchestrationPaused
A stale RUNNING session skipped by resumeAbandoned stayed RUNNING in
every projection — the TUI showed a phantom running workflow that no
process was executing, and each boot re-logged the same skip. Parking
now emits OrchestrationPaused(reason=ABANDONED_STALE): status becomes
PAUSED (truthful — not executing, resumable via POST /resume) and
subsequent boots skip it on status alone.
2026-06-12 13:54:08 +04:00
kami a455762dd5 fix(router,server,config,tui-go): close QA findings #2 #3 #9 #10 #12
#2: approval pauses narrate from ApprovalRequestedEvent (carries
tool/tier/preview/stage directly) instead of OrchestrationPaused +
a pending-approvals map the next event had not yet populated — the
narrator always lost that race and produced generic text.

#3: NarrationTrigger now carries the triggering event's stageId; the
narration status line and RouterNarrationEvent.stageId use it instead
of the router projection's currentStageId, which terminal events have
already cleared ('Stage: none').

#9: ExecutionPlanLocked -> plan.locked and ArtifactValidated ->
artifact.validated mapped to the TUI (Go: feed lines, plan shows the
compiled stage chain); ArtifactValidating explicitly dropped.

#10: blank router turn guard — a length-finish with empty text now
emits an explanatory turn naming the max_tokens budget instead of a
silent blank; blank steering responses no longer emit steering notes.

#12: resumeAbandoned only auto-resumes sessions whose last event is
within orchestration.resume_abandoned_max_age_minutes (default 24h,
0 disables); staler sessions stay parked for POST /resume. Knob is
TUI-editable and persisted.
2026-06-12 13:20:04 +04:00
kami 4107a595db refactor(server): single guarded freestyleHandoff shared by run and resume launchers
launchSessionRun and launchSessionResumeWithRehydrate each inlined their
own copy of the freestyle phase-2 handoff with different guard sets — the
run path had none, so a failed planning run still called lockAndRun and
emitted a spurious ExecutionPlanRejected. Both now call one handoff that
checks graph id, planning result, and an existing plan lock before
locking and running phase 2.
2026-06-12 12:32:57 +04:00
kami a0da220e8e fix(server): boot/event-driven resume paths get rehydrate + freestyle handoff
resumeAbandonedSessions (boot pickup of RUNNING sessions) and the
restart-while-approval-pending subscription still used the bare
launchSessionResume: no artifact-cache rehydrate and no freestyle
plan-lock handoff. A freestyle session killed mid-planning was grabbed
by the boot path before the operator could POST /resume, completed the
planning graph, and stranded — WorkflowCompleted but never locked
(live repro: session b36b0e45, 2026-06-12).

Both paths now use launchSessionResumeWithRehydrate; the bare launcher
is deleted.
2026-06-12 12:26:04 +04:00
kami e503a28db2 fix(server,kernel): freestyle survives kill/restart at every phase (B4)
Three gaps found by live QA (kill between planning-complete and plan lock):
- launchSessionResumeWithRehydrate never handed off to FreestyleDriver, so a
  resumed freestyle session stranded at planning-complete with no plan lock
  and no phase 2; now locks+runs when planning completed and no
  ExecutionPlanLockedEvent exists yet.
- resume route 400'd on phase-2 sessions (workflowId freestyle-<sid> is
  compiled, never registered); now recompiles the locked plan after
  rehydrating the artifact cache.
- orchestrator.resume threw when currentStageId was terminal ('done') or
  unknown to the graph; now returns WorkflowResult.Completed.
2026-06-11 22:35:51 +04:00
kami 50602b0f11 fix(server): bind project profile on chat-session start
Chat sessions never emitted ProjectProfileBoundEvent — only the workflow
path did — so router triage always saw a null profile and could not cite
conventions or commands. Extract the binding from launchSessionRun into
ServerModule.bindProjectProfile and call it from handleStartChatSession.
2026-06-11 22:09:34 +04:00
kami f521ac89e5 feat(memory): markdown heading extraction in RepoMapIndexer
h1-h3 headings become the file's symbols, so design docs and GDDs are
embedded and retrievable alongside code. .md files now also participate in
workspace fingerprinting (INDEXED_EXTENSIONS derives from pattern keys).
2026-06-11 14:39:05 +04:00
kami 81a21bfe57 feat(memory): GDScript symbol extraction in RepoMapIndexer
Adds 'gd' to SYMBOL_PATTERNS (func/class_name/class/signal/enum at column 0,
optional static prefix). Because INDEXED_EXTENSIONS derives from the pattern
keys, .gd files now also participate in WorkspaceStateProbe fingerprinting,
making fp: state keys content-sensitive for Godot repos.
2026-06-11 14:32:04 +04:00
kami 7a9e060a55 feat(kernel): RepoKnowledgeRetriever port + relevance-retrieved repo context with recency fallback 2026-06-11 13:39:57 +04:00
kami 9d38a89c88 feat(memory): state-keyed repo-map reuse via L3 presence check; embed entries on fresh scan
- Add existsByTurnIdPrefix(prefix) to L3MemoryStore interface; implemented in
  InMemoryL3MemoryStore (mutex-guarded) and TurboVecL3MemoryStore (metadata map).
  All test fakes (TrackingL3MemoryStore, CapturingStore) updated.
- Introduce RepoMapIndexerPort interface; RepoMapIndexer implements it. Allows
  injection of test doubles without framework overhead.
- ProjectMemoryService.indexAndRecord: reads latest WorkspaceStateObservedEvent
  from event store (invariant #9 — recorded fact, not re-probe); skips scan+embed
  when L3 already holds entries for "repomap:<root>:<stateKey>"; passes stateKey
  to RepoMapComputedEvent; embeds each scanned entry after append; per-entry
  failures warn-logged (CancellationException rethrown).
- Tests: InMemoryL3MemoryStoreTest +2 cases; new ProjectMemoryServiceReuseTest
  covers same-key skip, changed-key re-index, no-observation always-reindex,
  and embedding turnId tag verification.
2026-06-11 13:30:57 +04:00
kami 4dbe637f4a feat(memory): WorkspaceStateProbe (git or fingerprint) + observeAndRecord at session start 2026-06-11 13:22:50 +04:00
kami f1fc43cc85 feat(router,server): wire workflow inventory and project profile into router context per turn 2026-06-11 13:15:32 +04:00
kami ac458d83e5 feat(workflow): root description field in workflow TOML surfaced through registry 2026-06-11 11:08:33 +04:00
kami ed0dca8b78 feat(kernel,artifacts): inject artifact-kind vocabulary into architect context 2026-06-11 10:06:40 +04:00
kami 91cca9aee3 feat(server,kernel): bind ProjectProfile per session and inject as L0 context 2026-06-10 21:49:31 +04:00
kami 0de02bb3a7 feat(server,kernel): post-plan approval gate — operator reviews plan before lock
After a freestyle plan compiles successfully, requestPlanApproval is invoked
before ExecutionPlanLockedEvent is emitted. Rejection emits
ExecutionPlanRejectedEvent(source="operator") with no lock and no phase-2 run.
Adds public SessionOrchestrator.requestPlanApproval seam delegating to
requestStageApproval, wired in Main.kt. Two new tests cover the reject and
approve paths; existing tests stay green via the default passthrough param.
2026-06-10 21:36:17 +04:00
kami 3d43f34d3b fix(narration): use plan_rejected kind and include source in instruction 2026-06-10 21:31:34 +04:00
kami 04836b751c feat(server): narrate execution-plan rejection 2026-06-10 21:30:07 +04:00
kami 0553b1b6ee feat(events,server): ExecutionPlanRejectedEvent — freestyle compile failure visible in log
Both silent-failure branches in FreestyleDriver.lockAndRun now emit
ExecutionPlanRejectedEvent (source=compile or missing_content) before
returning, satisfying invariant #1. Event registered in eventModule for
correct polymorphic serialization.
2026-06-10 21:26:18 +04:00
kami 113f403f5a chore(examples): add qa_ping smoke workflow; ignore tui-go binary and qa/ scratch
qa_ping is a minimal single-stage file_write workflow used for live QA smoke
runs (writes 'pong' into the qa/ scratch repo). The qa/ workspace itself is a
nested git repo and stays untracked; the compiled tui-go binary is ignored.
2026-06-10 20:54:59 +04:00
kami c82a86477d fix(tui): wrap long artifact lines in viewer instead of truncating
Artifact content wider than the modal was silently cut off. Lines are now
hard-wrapped at rune boundaries to the modal content width, and scroll
clamping uses the wrapped line count so the last page stays reachable.
2026-06-10 20:54:39 +04:00
kami cba9dd4583 fix(server): artifact viewer showed FileWrittenArtifact envelope, not file content
file_written artifacts store a JSON envelope ({path, contentHash, size, mode})
whose contentHash points at the real bytes in CAS. The viewer rendered the
envelope itself. StreamQueries now detects the envelope shape (string "path" +
"contentHash") and dereferences the inner hash, falling back to the raw string
when parsing fails or the inner hash is missing (WARN logged — likely evicted).
2026-06-10 20:54:29 +04:00
kami 883e23dec7 feat(router,inference,config): pin narration to a configured model_id
[router.narration] model_id selects which provider handles narration turns
instead of generic capability routing — lets a small/fast model own narration
while the main model keeps CHAT/STEERING.

- NarrationSettings.modelId parsed from TOML, threaded via RouterConfig
  .narrationModelId into DefaultRouterFacade.narrate (3-arg route)
- DefaultInferenceRouter: exact-id match against healthy providers, with a
  post-select health check; any miss logs WARN and falls back to capability
  routing (never fails the narration turn)
- onUserInput unchanged — only narrate uses the pinned model (tested)
2026-06-10 20:53:28 +04:00
kami cc6b402a23 fix(server,kernel,events): harden artifact read + restore workspace on reopen + cleanups
Follow-ups spotted while fixing the artifact-content bug:

- #1 robustness: listArtifacts resolves content via runCatching, degrading to null
  instead of failing the whole listing on one bad CAS read (resolveContent helper)
- #2 eviction visibility: WARN when a referenced artifact hash resolves to null
  (most likely evicted/compacted while still referenced by the event log)
- #3 ordering trap: document on ArtifactCreated/ArtifactContentStored that 'Created'
  fires at validation, AFTER ContentStored at inference — the latent fold hazard
- #4 reopen gap: SessionSnapshot now carries workspaceRoot (derived from
  SessionWorkspaceBound), so a reopened session restores its workspace label
- #5 orchestrator headroom: isBackEdge externalised to a top-level function so
  DefaultSessionOrchestrator drops below the TooManyFunctions threshold (was at it)

#6 (orphaned empty F-010-era artifacts) needs no code change — empty content is
already normalised to null ('(no content stored)'); emission is prevented going
forward by the shipped F-010/F-018/F-020 fixes.
2026-06-10 12:58:28 +04:00
kami 5f7b6f1f18 fix(server): artifact viewer dropped content hash due to event ordering
Root cause of empty artifact content (even for role_pipeline sessions with intact CAS
data): ArtifactContentStored is emitted at inference time, BEFORE ArtifactCreated (which
fires at validation). listArtifacts only applied the content hash via accs[id]?.let{} —
a no-op because the artifact wasn't in the map yet — so the hash was silently dropped and
every artifact showed blank.

Verified against the live store (session 4679ed1e): all 5 artifacts had hash=none before,
now resolve to their validated content (analysis 867B, design 1074B, impl_plan 966B,
patch 574B, review_report 369B).

- ArtifactContentStored now seeds the accumulator (getOrPut), order-independent
- fold extracted to pure StreamQueries.planArtifacts for unit testing
- regression test: content hash captured when ContentStored precedes Created
- inference-output fallback retained for genuinely content-less stages
2026-06-10 12:49:11 +04:00
kami 90ba7244d5 fix(server): artifact viewer falls back to raw stage output when no content hash
Artifacts with no ArtifactContentStored mapping (older sessions, or stages where the
slot->hash link was lost) showed a blank pane. listArtifacts now falls back to the
stage's last InferenceCompleted output so the operator can still see what the stage
produced. Empty-string content is normalised to null so the viewer shows a clear
'(no content stored)' instead of a blank pane.

Verified: role_pipeline sessions already resolve content from CAS (index + segments
intact, store rooted at ~/.config/correx/artifacts); the previously-blank sessions are
healthcheck/degenerate runs whose inference output was genuinely empty.
2026-06-10 12:35:07 +04:00
kami 3c5c2999d3 fix(server,tui): restore output panel + full event history on session reopen
Reopening a session after relaunch sent only a thin summary snapshot, so the output
panel was blank and the event list showed at most 7 entries (of a 100+ event run).

- SessionSnapshot now carries lastOutput/lastResponse, resolved from the last
  InferenceCompleted artifact in CAS; tui-go onSnapshot restores the output panel
- recentEvents cap raised 7 -> 200 so a normal session's event list isn't truncated
- resolveLastOutput extracted as a helper

Note: artifact content is unaffected — verified intact and CAS-retrievable for
role_pipeline sessions (artread); content-less artifacts occur only in workflows that
record no ArtifactContentStored (e.g. healthcheck/process-result stages).
2026-06-10 12:22:54 +04:00
kami bb70c94a99 feat(kernel,events): freestyle graph re-routing — deterministic override core
Implements the replay-safe half of preemptive redirect (graph re-routing). An
operator-confirmed PreemptRedirectEvent overrides the resolver's edge at the next
transition boundary; the pure resolver stays untouched.

- PreemptRedirectEvent / PreemptRedirectBlockedEvent (+ serialization registration)
- TransitionExecutedEvent + TransitionDecision.Move gain optional redirectId — the
  durable 'consumed once' marker
- PreemptRedirect.decide: pure decision over the event log (override / block / none),
  so replay reaches the identical outcome without re-classifying (invariant #8)
- override wired in DefaultSessionOrchestrator.step above resolveTransition; back-edge
  jumps count against the existing maxRetries cap via executeMove
- blocks jumps to unknown stages or targets with unsatisfied needs
- DomainEventMapper: redirect events mapped to null (operator surface ships with the
  LLM-proposal + approval-confirm front-half)
- tests: override+consume-once, block-on-needs, block-on-unknown, ignore-consumed

Front-half (LLM proposal -> approval-gate confirm -> emit PreemptRedirectEvent) is
deferred; needs live LLM verification. Until it ships no redirect event is emitted in
production, so the override is inert. Spec: docs/plans/2026-06-10-freestyle-graph-rerouting.md
2026-06-10 11:51:47 +04:00
kami 8c9ca9db00 fix(tui): F-006 flatten multi-line paste in input render
A multi-line paste put raw newlines into the single-line input slot, overflowing
the fixed-height box and leaving a smeared/stale region. flattenForDisplay collapses
newlines (↵) and tabs for rendering only; the inputBuffer keeps real characters for
submission.
2026-06-10 11:13:45 +04:00
kami ee24b7786a fix(kernel,server,tui,inference): generic resource gauge + F-002/F-003/F-004
- generic SystemResourceProbe: owner-agnostic /proc/meminfo system RAM
  overlaid on the vendor GPU probe, wired on both managed and static paths
  so the VRAM/GPU/RAM gauge renders with an external llama-server (was dormant)
- F-002: classify provider 4xx (e.g. llama.cpp 400) as terminal, not retryable
  (except 408/429); stops retrying deterministic request failures to exhaustion
- F-003: FileSystemPromptLoader expands leading ~ / ~/ to user home
- F-004: map InferenceFailedEvent + RetryAttemptedEvent to new
  ServerMessage.InferenceFailed / RetryAttempted, decoded+rendered in tui-go;
  ArtifactContentStoredEvent explicitly mapped to null (internal, no warn)
- tests: tilde expansion, SystemResourceProbe (static/managed/fail-soft)
2026-06-10 11:07:08 +04:00
kami a92b5a3531 feat(tui,server,config): live config editor + artifact viewer
Two operator features over the existing WebSocket protocol, plus a tui-go nav fix.

Config editor (g / palette "config"):
- core:config gains a thin registry stack: OrchestrationKnobs ([orchestration]
  block), CorrexConfigWriter (round-trip TOML serializer; parseToml(write(c))==c),
  ConfigHolder (AtomicReference live config), and EditableConfig (allowlist of
  editable fields + patch applier; security fields are absent so they can never
  be patched).
- ConfigService validates -> persists (atomic temp+move) -> swaps the holder ->
  rebuilds config-derived services. Live-apply is scoped to safe seams: stage
  timeout, compaction threshold (now a supplier), router knobs (routerFacade
  rebuild), and personalization/project toggles all take effect on the next
  session/turn; in-flight sessions keep the config they started with.
  server.host/port are persisted + badged "restart required", not hot-swapped.
- Protocol: GetConfig / UpdateConfig / ConfigSnapshot; TUI overlay with
  type-aware editing (bool toggle, enum cycle, inline int/string edit) and save.

Artifact viewer (v / palette "artifacts"):
- Server assembles a session's artifacts from the event log and resolves bytes
  from the CAS store (StreamQueries.listArtifacts) — a replay-neutral snapshot
  read. TUI overlay lists artifacts and shows scrollable content.

tui-go fix: workflow failure no longer clears selectedID (which yanked the user
to the list); listNav lands on the first session when nothing is selected
instead of wrapping to a random one.

Config is not event-sourced (it lives in TOML); editing stays outside the log.
2026-06-09 10:18:35 +04:00