Skip to content

Add conversation history, document attachments, and safer server binding - #74

Open
bayeasscamera wants to merge 20 commits into
drumih:mainfrom
bayeasscamera:feature/conversation-history-attachments
Open

Add conversation history, document attachments, and safer server binding#74
bayeasscamera wants to merge 20 commits into
drumih:mainfrom
bayeasscamera:feature/conversation-history-attachments

Conversation

@bayeasscamera

Copy link
Copy Markdown

Summary

Adds three independent improvements to the Mac app and the loopback server.

Mac app: conversation history

  • Persistent sidebar (NavigationSplitView) listing past conversations, with search, load, delete, and clear-all.
  • Conversations are stored as JSON in Application Support and include token counts and stop reason from diagnostics.

Mac app: document attachments

  • Attach PDF, DOCX, TXT, MD, and RTF files (up to 50 MB); extracted text is injected into the prompt with [Document: …] context markers.
  • Text extraction via PDFKit for PDFs and NSAttributedString for DOCX; files over 500k characters are truncated with a visible warning.
  • Supported through the file importer and drag & drop.

Fixes

  • Restore InspectorView in the detail pane of the NavigationSplitView. The sidebar refactor had dropped the runtime controls (context length, expert-cache slots, temperature/Top-K/Top-P, prefill, RDADVISE, unload) from the layout entirely.
  • Remove dead cognitive-mode placeholder (runCognitiveCycle) that skipped generation state initialization (transcript reset, activeRunRuntimeKey, live counters).
  • Release security-scoped access after document extraction: startAccessingSecurityScopedResource() was never balanced with stopAccessingSecurityScopedResource(), leaking access tokens; drag-and-drop URLs had no scope handling at all.

Server hardening

  • Add --host and --allow-remote. Binding a non-loopback host is refused unless --allow-remote is passed explicitly, since the server has no authentication or TLS.

CI

  • Cache SwiftPM, add a debug build as a fast first signal, and run swift-format lint as a non-blocking step.
  • Ignore local TurboFieldfare.app bundle builds.

Validation

  • swift build (debug): clean
  • Scripts/test.sh: 520 tests in 108 suites, all passing (includes 5 new tests covering --host/--allow-remote parsing)

Mac app:
- Persistent conversation sidebar with search, load, delete, and clear
- Attach PDF/DOCX/TXT/MD/RTF documents; extracted text is injected
  into the prompt with context markers
- Restore InspectorView as the NavigationSplitView detail pane
  (runtime controls were dropped by the sidebar refactor)
- Remove dead cognitive-mode placeholder that skipped generation
  state initialization

Server:
- Add --host and --allow-remote; binding a non-loopback host is
  refused unless --allow-remote is passed explicitly

CI:
- Cache SwiftPM, add a debug build as a fast first signal, and run
  swift-format lint as a non-blocking step
- Ignore local TurboFieldfare.app bundle builds
startAccessingSecurityScopedResource() was called on every imported
URL but never balanced with stopAccessingSecurityScopedResource(),
leaking access tokens for the whole session. Move the scope handling
into AppModel.attachDocuments and always release it with defer after
extraction. This also covers drag-and-drop URLs, which previously
had no scope at all.
ConversationStoreTests: insert/update ordering, delete, clear-all,
JSON persistence across store instances, missing-file startup, and
Conversation title/response preview formatting.

DocumentAttachmentTests: extraction from real TXT, MD, RTF, DOCX, and
PDF files (generated in-memory), unsupported-format, oversized (sparse
file), and empty-document errors, long-text truncation, and attachment
prompt context/preview/size presentation.

AppModelAttachmentTests: attachDocuments extraction and error surfacing,
promptWithAttachments composition, makeRequest prompt injection, size
totals, and removal/clear helpers.
@bayeasscamera
bayeasscamera force-pushed the feature/conversation-history-attachments branch from 5046a3b to ff7f73c Compare August 3, 2026 13:59
Documentation:
- README: conversation history sidebar, document attachments section,
  --host/--allow-remote mention, status list update
- OPENAI_SERVER.md: bind-address section explaining --allow-remote

Conversation history:
- Inject ConversationStore into AppModel (tests no longer touch real
  Application Support data)
- Regenerating the same prompt updates the active conversation instead
  of creating duplicates; a new prompt starts a new entry
- Persist document attachments in Conversation (Codable) with legacy
  JSON decode fallback; loadConversation restores them
- Replace the single attachmentError with an attachmentErrors list so
  every failed file surfaces in the composer

Tests: 13 new (injectable store, upsert semantics, attachment round-trip,
legacy format decoding, conversation lifecycle) — 613 total passing.
…ttachment limits

Sidebar:
- Pin conversations to the top of the list (pinned sort first)
- Rename inline via double-click, context menu, or keyboard
- Export/import history as JSON (file exporter/importer)
- Keyboard shortcuts: ⌘N new conversation, ⌘F focus search

Attachment limits:
- DocumentExtractor takes instance limits (defaults unchanged)
- New persisted settings for max file size and extracted text length
- InspectorView Attachments section with pickers

Tests: pinned ordering, rename behavior, export/import round-trip and
id replacement, custom extractor limits — 622 total passing.
The app never loads the tokenizer in-process, so the composer hint uses
a character-based estimate of the effective prompt (documents included).
The capsule turns orange with an explanatory tooltip when the estimate
exceeds the selected context window.

README: document pinning, renaming, export/import, configurable limits,
and the token hint.
CI:
- swift-format lint now covers only the Swift files a PR changes
  instead of the whole tree (the codebase predates swift-format, so a
  global run reports 26k style warnings and is unusable as a signal)

Conversation store:
- Log decode/save failures with os.Logger instead of failing silently
- Accept fractional-second ISO8601 timestamps on decode so history
  files written by other tools still load

Tests: fractional timestamp decoding — 626 total passing.
When enabled (inspector > Runtime, persisted), generation runs four
passes instead of one. Each pass streams into the output pane under
its own header; the conversation history stores the final revision.

- CognitiveCycleEngine: pure stateful builder of the four prompts,
  each embedding the outputs of the previous passes
- AppModel orchestrates the passes with per-pass HUD resets; cancel
  stops after the current pass; failures end the run like single-pass
- Save conversation with an explicit response so history keeps the
  revised version rather than the whole cycle transcript

Tests: engine ordering, prompt composition, idempotent nextStep,
completion; full cycle through MockInferenceClient (four passes,
final revision in history, cancel mid-pass) — 640 total passing.
…s, history safeguards

- Scripts/build-app.sh + install-app.sh: bundle (+ ad-hoc signature),
  version from git tag (--version to override), --force install
- CLI --cognitive-mode: runs the plan/draft/critique/final cycle; engine
  moved into the shared runtime so both app and CLI use it
- Per-conversation maxNewTokens: persisted and restored on load (B.2)
- Attachment errors: per-file list with individual dismiss + Clear all
  (C.4); alert on unreadable history file (preserved as .corrupted) (C.3)
- Cognitive passes segmented in-memory for future UI polish (B.1 foundation)
- Tests: history recovery, maxNewTokens, CLI flags, cognitive engine
  round-trip — 649 total passing
Conversation now stores ordered turns instead of a single exchange.
Loading a history entry restores the full transcript and clears the
composer so the dialogue can continue: the next message is sent with
the whole turn history, and regenerating the same prompt replaces the
previous model answer instead of appending a duplicate.

- Turn model + legacy JSON migration (prompt/response keys still decode)
- continuationPromptText composes history + new user message (documents
  included); makeRequest uses it when a conversation is active
- saveCurrentConversation appends turns, or replaces the last answer
  when the same prompt is regenerated
- Sidebar search covers turn text; composer placeholder switches to
  "Continue the conversation…"; README updated

Tests: asPrompt/displayTranscript, continuation composition, append and
replace semantics, legacy migration — 657 total passing.
- G.3: extracted PDFs separate pages with [Page N] markers so answers
  can cite where information came from
- F.2: fork a conversation from the context menu (optionally up to a
  given turn); forks link to their parent via parentConversationID
- H.4: per-conversation Markdown export via the context menu with a
  filesystem-safe filename

Tests: fork semantics (full + up-to-turn), Markdown rendering — 660
total passing.
- F.3: mark any conversation as a reusable template; a Templates
  filter button in the sidebar shows only those
- F.4: per-conversation tags (context menu > Edit Tags), #tag search
  in the sidebar; tags and template flags survive history upserts
- C.2: install.log journal in Application Support records install
  phase transitions and failures for diagnostics

Tests: template flag, tag normalization/dedup, flag preservation on
regeneration — 663 total passing.
- C.4: --export-conversations <path> copies the app conversation
  history JSON (validated) without requiring a model
- I.3: GET /metrics exposes Prometheus-style counters for served
  completions, uptime, and model info

Tests: metrics endpoint state and counter growth — 665 total passing.
- B.1: after a cognitive cycle finishes, the output pane shows each
  pass as a collapsible disclosure group instead of one long transcript
- H.2: CognitiveStopPolicy detects critiques that found nothing to fix
  and skips the final revision pass, keeping the draft as the answer

Tests: stop policy (empty/positive/actionable critiques) — 668 total
passing.
- H.3: compare any conversation with the latest run of the same prompt
  in a side-by-side sheet (context menu > Compare with latest run)
- H.1: Summarize starts a new conversation whose first turn asks the
  model to summarize the selected history, then generates immediately

Tests: shared-prompt filtering, summary launch through the mock client
— 670 total passing.
- G.2: DocumentLibrary stores extracted documents by filename in
  Application Support; attachDocuments caches new extractions there
- G.1: when attached documents would overflow the context window,
  promptWithAttachments keeps only the chunks most relevant to the
  prompt (word-chunk overlap + TF-IDF scoring, pure Swift)

Tests: chunking (short/long/overlap), retriever ranking and fallbacks,
library upsert/lookup/persistence, extraction caching — 680 total
passing.
- I.1: chat messages accept an optional document reference (inline
  text, file path, or base64); text is injected into the prompt with a
  [Document: name] marker. Binary formats are refused with a clear error
- I.2: GET /v1/conversations serves the app's history JSON (empty array
  when no history file exists)

Tests: extractor (inline/base64/path/errors), prompt injection through
a recording backend, conversations route — 689 total passing.
The previous version had a typo in the build_args expansion that
caused zsh to fail when called via bash. Pin destination and
application_dir directly; use  so the build step
succeeds even when no flags are passed.
- TurboFieldfareAppearance: shared tokens (dark background, surface,
  accent/danger, corner radii) used across the app
- RootView/Inspector/HUD/Examples/Error/ModelAction surfaces now use the
  dark surface instead of system window backgrounds
- Sidebar rows show pin and template glyphs plus tag badges (max 2 + N)
- Composer: raised surface, accent focus ring, iconic placeholder,
  continued-conversation hint
- Attachment rows: tinted icon tiles and accent hover stroke
- Cognitive passes render as tinted, collapsible cards per pass
- Install progress bar tinted with the accent color

No behavior changes; 689 tests still passing.
- Terminal responses now render as formatted Markdown (headings, code,
  lists, emphasis) instead of raw text; the prompt stays above in
  italic gray. Streaming keeps the live NSTextView until generation ends
- Prompt example cards get accent hover states (fill + stroke) via a
  dedicated subview to keep type-checking tractable

Pure visual changes; 689 tests still passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants