Commit Graph
69 Commits
Author SHA1 Message Date
sorokin.oleg.edu ece228e8ed test: Added sandboxed, isolated Chromium first-run coverage with stat...
- package.json
- package-lock.json
- .gitignore
- playwright.config.ts
- e2e/fixtures.ts
- e2e/smoke.spec.ts
- tests/svg-image.test.tsx

GSD context:
- Milestone: M001 - Merge upstream v0.4.0 into the Gitea fork
- Slice: S02 - Verify combined application behavior
- Task: T01 - Added sandboxed, isolated Chromium first-run coverage with stateful synthetic API fixtures and fail-closed network boundaries.

GSD-Task: S02/T01
2026-10-31 16:20:49 +03:00
sorokin.oleg.edu 9727516926 Merge upstream v0.4.0 into Gitea fork 2026-10-31 13:45:06 +03:00
amosblomqvist dc4c398d51 release: prepare 0.4.0 2026-10-29 06:26:20 +02:00
amosblomqvist 8883fe1f5f Merge native Claude Code integration with explicit opt-in
# Conflicts:
#	src/agent/teacher-agent.ts
#	src/server/keys.ts
2026-10-28 21:45:50 +02:00
amosblomqvist eeaa147565 Merge model-visible image processing and desktop file-path attachments 2026-10-28 19:54:32 +02:00
amosblomqvist 84f93999c5 fix: normalize model-visible images with Pi processing 2026-10-28 18:16:04 +02:00
amosblomqvist 812ea23419 Merge current main into PR61 finishing branch 2026-10-28 07:08:36 +02:00
amosblomqvist 59f389d0a6 fix: upgrade Next to bound gzip drain listeners 2026-10-28 00:20:49 +02:00
amosblomqvist b577ec5453 Prepare 0.3.9 release 2026-10-27 23:12:39 +02:00
sorokin.oleg.edu d43ffe4f8a chore: prepare 0.6.0 Linux and Windows release 2026-10-26 23:43:56 +03:00
amosblomqvist 3dc88472ee chore: prepare 0.3.8 2026-10-26 06:08:04 +02:00
amosblomqvist 8c277f8320 Merge source-preserving math content presentation 2026-10-26 05:26:13 +02:00
amosblomqvist d3c8709bd2 fix: preserve source data and recover tool math at presentation 2026-10-26 04:49:47 +02:00
sorokin.oleg.edu 168cad7470 chore: prepare 0.5.2 Linux and Windows release 2026-10-26 02:14:27 +03:00
amosblomqvist 46c95c7b59 fix: update Electron to 44.9.3 2026-10-26 00:15:59 +02:00
sorokin.oleg.edu fdd63b27a9 chore: prepare 0.5.1 Linux and Windows release 2026-10-26 00:58:26 +03:00
sorokin.oleg.edu d2d9fcd09e chore: prepare 0.5.0 Linux and Windows release 2026-10-25 23:00:45 +03:00
Ivanko1608 25a4687611 Merge remote-tracking branch 'origin/main' into add-claude-code-support
# Conflicts:
#	src/agent/models.ts
#	src/server/model-catalog.ts
2026-10-25 19:36:30 +04:00
sorokin.oleg.edu 2da091d6e9 fix: replace inherited updater provider in Gitea packages 2026-10-25 17:41:43 +03:00
sorokin.oleg.edu 674d4bd8c6 chore: prepare 0.4.0 Linux release on Gitea 2026-10-25 17:33:37 +03:00
amosblomqvist 9171a4f49c chore: prepare 0.3.7 2026-10-25 02:27:57 +03:00
Ivanko1608 49181a1a8d Merge remote-tracking branch 'origin/main' into add-claude-code-support
# Conflicts:
#	package-lock.json
#	src/agent/models.ts
2026-10-25 02:08:15 +04:00
amosblomqvist 74b0c65843 release: prepare 0.3.6 2026-10-24 20:07:44 +03:00
Ivanko1608 8981456dfd chore: add MCP SDK and CLI stream fixtures
Pin @modelcontextprotocol/sdk 1.32.1 for the tool bridge. Fixtures are
sanitized `-p` stream-json recordings (text, tool, signed-out).
2026-10-24 20:24:57 +04:00
amosblomqvist 50a2db775d release: prepare 0.3.5 2026-10-24 16:23:39 +03:00
amosblomqvist 0fc6d4ab94 feat: add verified desktop ChatGPT subscription connection 2026-10-24 12:17:08 +03:00
amosblomqvist ae2fad56c4 chore: prepare platform preview 0.3.5-preview.1 2026-10-24 10:43:51 +03:00
amosblomqvist 7fb891389d fix: retain Google provider dependencies in desktop builds 2026-10-24 08:15:04 +03:00
amosblomqvist 6cba9c8c5a release: prepare 0.3.4 2026-10-23 20:29:02 +03:00
amosblomqvist cc657faa93 Merge native Pi identity and provider catalog upgrade 2026-10-23 17:25:10 +03:00
amosblomqvist 6685b3d09a fix: use native Pi identity and refresh provider catalogs 2026-10-23 17:25:10 +03:00
amosblomqvist 93017d767e security: upgrade KaTeX to 0.18.2 across active renderers 2026-10-23 17:11:57 +03:00
amosblomqvist 388addff3e release: prepare 0.3.3 2026-10-23 06:18:51 +03:00
amosblomqvist 1c5024c8c2 release: prepare 0.3.2 2026-10-21 00:41:53 +03:00
amosblomqvist 0ebb88ec08 release: prepare 0.3.1 2026-10-20 16:40:43 +03:00
amosblomqvist c32d7a89af build: fix macOS CI signing keychain password handling 2026-10-17 14:48:32 +03:00
amosblomqvist 6b29688c47 release: prepare 0.3.0 and README screenshots 2026-10-17 14:09:48 +03:00
amosblomqvist 91672160c2 release: add storefront artwork and draft completeness gate 2026-10-13 15:41:43 +03:00
amosblomqvist cf042b25e7 icons: per-platform icon set — real Liquid Glass on macOS, flat design on Windows/Linux
The hand-packed icns was pixel-clean (verified by unpacking and scanning
every size), but macOS 26 composites legacy .icns icons into the Liquid
Glass system at render time; stacked on Icon Composer's baked PNG-export
lighting, that clips the rim highlights into the near-corner specks and
abrupt highlight ends (owner report 2026-10-13). The fix is the real
layered icon: mac.icon now points at the Icon Composer document
(build/icon.icon, copied from ~/Documents/Affinity/lh-logo) and
electron-builder compiles it via actool into Assets.car — macOS 26+
renders the true Liquid Glass design (dark/tinted variants included).

actool's backwards-compat icns caps at 256px, so scripts/after-pack.cjs
swaps in the existing full-res build/icon.icns before signing: pre-Tahoe
macOS keeps today's quality, Tahoe gets the layered icon.

Windows/Linux get the owner's flat design (rounded squircle with baked
transparency): scripts/make-icons.mjs renders build/icon-flat.svg via
resvg into build/icon.ico (16→256 PNG entries) + build/icon.png (512),
and electron-builder.yml pins win.icon/linux.icon.

desktop:dist injects DEVELOPER_DIR (actool needs Xcode; the machine
selects CommandLineTools by default, CI's setup-xcode takes precedence
via the :- default). The release workflow's mac job selects Xcode 26+
explicitly. Verified locally: mac --dir build (Assets.car + checksummed
full-res fallback + CFBundleIconName/IconFile) and the win-unpacked exe
carries our ico bytes; NSIS/AppImage assembly is CI's (cross-build
tooling can't run on arm64 macOS). Runbook updated. 1610/1610.
2026-10-13 10:51:22 +03:00
amosblomqvist e266c3c028 desktop: tri-platform window chrome, packaging and draft-release CI 2026-10-11 10:12:13 +03:00
amosblomqvist e9cf841ce7 wave E stage 2: renderSvg goes native-first - resvg in-process
Visual-maker SVGs never execute script, so a browser added nothing but a
process. resvg rasterizes in-process now (white background and fitTo
width mirror the old Chrome-page semantics; svgViewport unchanged,
quirks included for parity). The old Chrome path stays verbatim as the
fallback for exotic-SVG parse failures - it was the only path before,
so nothing regresses.

The resvg binary (4MB per platform, one per build) rides the standalone
via the same tracing pattern. Gates 1519/1519; native path pinned
against real SVGs (intrinsic size, viewBox-only 900 default, opts
override, system-font text).
2026-10-11 06:52:32 +03:00
amosblomqvist e8e00a5237 auto-update wiring: silent download, restart banner, local-feed drill seam
electron-updater in the shell (packaged builds only; dev never updates).
Checks at startup + every 24h, downloads silently, and the renderer shows
a Restart banner once the update is staged (it installs on quit either
way). Manual "Check for Updates..." under the app menu answers via dialog.
Every updater event routes through the shell log and is caught - the
updater can never take the app down (verified live: a feed failure logs
three lines and the app runs on).

LH_UPDATE_FEED overrides the baked GitHub feed with a generic local
server - the pre-release drill: serve latest-mac.yml + zip + blockmap
from localhost and a real installed copy updates itself, no release, no
public repo.

The asar allow-list re-includes electron-updater + its 16-package runtime
tree explicitly (gitignore last-match-wins; asar verified at 2.1 MB).
The banner rides the preload bridge (onUpdateStatus/installUpdate) and
renders nothing in the browser. window.lh typing centralized in
sidebar.tsx's declaration.
2026-10-10 14:11:19 +03:00
amosblomqvist 0a7d12c364 repo: public housekeeping — license, product name, docs diet
- LICENSE: AGPL-3.0-only (holder Amos Blomqvist), package.json gains "license".
- Product/package name unified: package.json "name" is now "learnharness"
  (matches the GitHub repo); electron-builder productName/appId already
  were LearnHarness/com.learnharness.LearnHarness.
- Deterministic userData: app.setName("LearnHarness") early in main.cjs
  — Application Support dir is exactly "LearnHarness" in dev and
  packaged runs (packaged Electron would otherwise derive it from the
  package name). Dev shell becomes "LearnHarness-dev". Pre-release
  rename: zero external users, no migration code; the local test dir
  was moved by hand.
- docs/ diet: nine vision-*.md splinters, the completed pi-ai migration
  plan, and research/ notes leave the tree (retained privately in the
  git-ignored memory; full history stays in git). Public docs remain:
  architecture, vision, plan, probe-teach-split, pi-engine-swap,
  keyboard, teaching-methodology (+ release-0.3 until the ship).

Gates: 1489/1489, typecheck, lint.
2026-10-10 11:20:20 +03:00
amosblomqvist 5c1eb47740 packaging: signing + notarization + zip target + GitHub Releases feed
The 2026-10-08 flip, verified end-to-end (notarized build accepted,
stapled, spctl clean): Developer ID auto-detect, LH_NOTARY keychain
profile, zip + blockmap beside the DMG for Squirrel.Mac, publish block
pointing at GitHub Releases (repo goes public before 0.3.0; builds never
auto-upload — publishing is a release-time act).
2026-10-09 07:59:32 +03:00
amosblomqvist 8cb5e14691 desktop: the payload fixed — 7.4GB→144MB, dev keys out, boot-smoke gate
- desktop:dist cleans dist/ + .next/standalone before next build: the
  trace sweep was shipping the PREVIOUS build's DMG inside every new one
  (standalone/dist = 8.0GB inside the app; measured 7.4GB DMG → 144MB).
- the same sweep shipped the repo-root auth.json (dev BYOK keys,
  byte-identical) in every DMG ever built — excluded in the trace and
  REFUSED by a new build-server.mjs guard (.env* belt-and-braces).
- WARNING (next.config.ts): dir-style trace excludes (dist/** etc.,
  "./"-anchored or not) strip server-external packages to bare
  package.jsons — pi-ai/pi-agent-core lost their dist/, the packaged
  server 500'd on ERR_MODULE_NOT_FOUND. Only the three verified-safe
  entries stay.
- new scripts/smoke-server.mjs gates desktop:dist between payload
  curation and packaging: boots the standalone exactly as the Electron
  shell does (temp LH_DATA_DIR, empty auth) and requires 200s on / +
  graph/models/settings. Caught both defects above on its first day.
- entitlements: allow-unsigned-executable-memory dropped (2019-era
  relic per current electron-builder docs; weakens hardened runtime).
- pi-ai + pi-agent-core pinned exact 1.0.2 — persisted chat records
  carry their wire shapes; post-release a bump is a migration event.

Gates: typecheck, lint, 1459/1459 tests, full desktop:dist + smoke PASS.
2026-10-08 17:35:15 +03:00
amosblomqvist 02e7840b2f pi 1.0.2: bump @earendil-works/pi-ai + pi-agent-core 0.87.1 → 1.0.2
All imports and Agent seams survive (verified pre-bump). Fallouts, all
catalog drift handled by the documented refresh path:
- sync-pricing.ts: catalog schema v6 keys are type-qualified (chat:id);
  price chat models only, strip the prefix so PRICES keys stay provider:id
- prices.ts regenerated (1537 keys); auth-keys gains the typesafe provider
- test pins: glm-5.3-flash contextWindow 1048576→1048575, cacheRead
  0.05→0.03 (activity-feed, model-catalog, render-activity-dock, usage)

Inherited free: at-capacity retry, unfinished-tool-call error handling,
model samplingParams no longer dropped on direct stream().
2026-10-04 19:50:20 +03:00
amosblomqvist 876185eb6e chore: pi family 0.85.1 -> 0.87.1 (finishTurn, transcript contexts, typebox 1.3.27)
Mechanical fallout only; semantic deltas flagged in the wave report:
- shouldStopAfterTurn -> finishTurn ({action:'end'} wrap; loop order verified
  equivalent for Wave C stranding)
- transcript rework: seeded leading SystemMessage filtered at both
  state.messages read seams (ui-stream settleWith, pi-run slice); prompt
  and tool declarations now ride the transcript
- streamOnce sites normalizeContext() (Context -> TranscriptContext)
- AgentToolResult details / ToolCall.arguments JsonValue narrowing (casts)
- AUTH_PROVIDER_KEYS regenerated (meta added); glm-5.3-flash price pin
  follows the 0.87.1 catalog (0.15/0.5/0.05)
- vanish probe: 0/12 vs 2/6 baseline; live smoke green on PORT=3004
2026-09-26 21:45:51 +03:00
amosblomqvist f3003d357a Wave D: native AgentEvent wire + client session rewrite (SDK purged)
Server: ui-stream.ts is verbatim pi AgentEvent passthrough (SSE data
lines + [DONE]); only app-level events are the loop-level error and
turn-settled terminal; onError carries raw provider text (owner ruling).

Client: useChat + the AI-SDK transport replaced by components/session-core.ts
(SessionModel: 409 identity-retry, optimistic c_ appends, absorbed()
adoption, needsContinuation, shared consume for POST + reattach) and the
session.ts hook; the golden-master grouping canon moved client-side into
core/view.ts (projectView); scanners read the native record (phaseAt3Lh,
danglingQuestionOf, graph toolParts, plan-blocks, runner, extraction).

Coverage: the native message is the unit (fork 3) with the call+result
inseparability bond rule; view-line-less messages (markers, error
results) are invisible to coverage and never checkpointed.

Deletes: chat-transport, projection, ui-message, their tests (ported to
tests/session-core.test.ts), and the ai + @ai-sdk/react dependencies —
the Vercel SDK is fully out of the tree. Wire pins in chat-flow rewritten
onto the AgentEvent dialect (eventsOf parser).

Gates: tsc clean, 994/994 tests, eslint clean, build green; live smoke
on PORT=3004 (verbatim wire → native record).
2026-09-26 01:52:47 +03:00
amosblomqvist b87a6925af Wave A: pi-native tool layer — delete LhTool/toPiTool, TypeBox everywhere, terminate on questions
- src/agent/lh-tool.ts deleted: the zod-wrapped LhTool + toPiTool bridge
  (z.toJSONSchema per call, clientQuestion flag, hand-rolled result mapping)
  is gone. Tools are pi-agent-core native AgentTools now.
- New src/agent/tool-execute.ts owns what the wrapper added: the abort guard
  (runWithAbortGuard: run signal + 30s per-call timeout + 250ms grace,
  'Operation aborted' / timeout wording preserved), ToolInputError (the
  retryable '- problem at path' format), result mapping (string verbatim /
  object JSON / image parts, details rides the record's tool part output),
  and questionResult() — placeholder + terminate:true (owner Option A:
  belt of terminate + slim makeQuestionStopper with the clean-question
  rule; maxTurns deleted from the teaching turn entirely per owner).
- All 11 tool files + tools.ts registries converted to TypeBox parameters
  (typebox@1.3.7, pinned to pi-agent-core's copy). zod refinements
  (quizInputProblems, askOptionProblems, live-nodeId) moved from schemas
  into execute — the model sees the same retryable error shape.
- question-core + contracts/probe TypeBox-authored (single source shared
  with the UI); tool-parts parse seam runs the boundary's full chain
  (Convert → Check → Clean → refinement), fixing the live-smoke lockout:
  pi's boundary Value.Converts correctAnswer: 17 → '17' but the record
  keeps raw args, so a parse without Convert rejected a posed quiz
  (invisible pending question, turn ended). Pinned with the real smoke
  emission shape.
- Subagents converted: demo-builder/researcher AgentTool-native,
  visual-maker/pdf-reader declaration-only pi-ai Tools with local execute
  fns, extraction submit schemas re-authored in TypeBox (owner option 2:
  zod defaults → Type.Optional + fill at the submit seam).
- Accepted delta, flagged: TypeBox-native schemas skip pi's JSON-Schema
  primitive coercion fast path... (boundary still Value.Converts), and
  prober-quiz unknown keys ride through (Value.Clean strips at display).
- Tests: 988 passing (was 967 at the recon baseline); tool-abort and
  read-tool rewritten against the native seams; question-core/question-
  tools/tool-parts/demos/web-search/graph-tools/plan-tree/probe-done/
  tool-toggles converted to Value.Check/execute pins.

Live smoke (PORT=3004, real model): native ls turn streams end-to-end; a
real quiz pose (first attempt missing correctAnswer → tool-input-error →
model retry → posed) terminates the turn cleanly and persists pending.
2026-09-25 23:32:13 +03:00
amosblomqvist 0a4a9a11de BYOK provider embeddings (wave 6 chunk B): kill local transformers
Replace the @huggingface/transformers local embedder (~316MB ONNX cache)
with an EmbeddingPort seam: graph.embeddingModel (nullable "provider:id"
slug) selects an OpenRouter/OpenAI/Gemini adapter, resolved fresh per
call from settings + the auth store so key rotations land immediately.

- src/server/embeddings.ts rewritten: slug grammar, injected-fetch
  adapters (OpenRouter input_type, Gemini taskType batching, OpenAI
  index-ordered rows), honest resolution (null = skip-with-info, set
  model with missing key = honest error), embedNewNodes with model-change
  re-embed wipe + ghost pruning + usage steps, federated catalog
  (OpenRouter /api/v1/embeddings/models — VERIFIED live, the inverse
  path 404s; OpenAI text-embedding- filter; Gemini embedContent filter).
- GET /api/models/embeddings: federated catalog, empty when no keys.
- Extraction tail records stages.embedding usage into the run's money
  channel; EMBEDDING_PRICES overlay in core/prices.ts (outside the
  sync-generated block), callCost falls back to it; cost-summary folds
  the fourth stage; graph-context injection resolves its embedder per
  run, fails honestly when unconfigured, records query tokens.
- Settings: GraphSettings.embeddingModel (null factory), section parser
  validates slug shape (id may contain colons — OpenRouter :free
  suffixes, live catalog fact).
- Removal: @huggingface/transformers uninstalled, PATHS.modelCache and
  onnx tracing entries deleted, @huggingface tracing-excludes guard
  added, stale sharp/libvips comments updated to historical.

Live-verified on port 3004: catalog federates 36 rows (33 OpenRouter +
3 OpenAI), embedNewNodes embedded 10 nodes via the free liquid/lfm-2.5
model (dim 1024, 435 prompt tokens, 1 call, $0), settings PUT rejects
bad slugs with 400.

Gates: typecheck, lint, 895/895 tests, build.
2026-09-23 03:53:57 +03:00