Commit Graph
33 Commits
Author SHA1 Message Date
marcopanandClaude Fable 5 2410f01b34 fix(bridge): forward Pi agent_end so the spinner stops at workflow completion
The FE derived 'working' purely as activeSession && !pendingWidget, so the
final workflow turn — the only one that ends without a follow-up gate —
left the spinner on forever (observed live: 21592s after F8 approve).

- SessionBridge maps Pi's agent_end -> SSE system_event {event: agent_end}
- PiProcessManager notifies the client (info error + synthetic agent_end)
  when the child dies unexpectedly; expected teardowns stay silent
- sessionStore tracks agentActive (on: user entry/text_delta/ui_request,
  off: agent_end); AppShell working now requires it; resume sets it
  optimistically

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:14:43 +02:00
marcopanandMarco Pancotti ad5a4d938c test(backend): pin schema-linking structured response passthrough 2026-07-06 23:25:00 +02:00
marcopanandClaude Opus 4.8 418187a4ad fix(backend): echo Pi's RPC id so reviewer gates unblock after answer
ctx.ui.input in `pi --mode rpc` correlates extension_ui_response on its own
top-level RPC id (crypto.randomUUID), not the descriptor id the gate carries
in `title`. SessionBridge replied with the descriptor id, so Pi silently
dropped the response and the model never resumed — every reviewer widget hung
after the human answered.

SessionBridge now stores Pi's top-level m.id (pendingPiId) and replies
extension_ui_response{ id: pendingPiId, value: <uiResponse> }; value still
carries the descriptor id so the gate's internal resp.id === descriptor.id
check still holds.

The fake-pi double had masked the bug by forcing m.id == descriptor.id; it now
mirrors real Pi (distinct randomUUID, correlate on it, drop unknown ids), with
a negative regression test. SKILL.md Phase 1 also now steers multi-answer
disambiguation to reviewer_decide (multiselect).

Tests: backend 67/67, tsc clean, fake-pi contract 2/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:43:49 +02:00
marcopanandClaude Opus 4.8 37d40d6680 fix(backend): drop pi --approve flag (removed in pi 0.73 @mariozechner rebrand)
pi 0.73 rpc mode is headless and runs tools without an approval gate; the
removed --approve flag made pi exit with 'Unknown option: --approve', breaking
every session spawn. Spawn args are now just --mode rpc. +regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:03:48 +02:00
marcopan a791919925 fix(backend): check read-only 409 before the Ollama preflight on resume 2026-06-29 20:06:03 +02:00
marcopanandClaude Sonnet 4.6 90a26dafce feat(backend): Ollama embeddings preflight on session create/resume (503 hard-fail)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 19:37:39 +02:00
marcopan a0af089d00 feat(backend): ThtRunner.ollamaEnsure (parses tht ollama ensure --json) 2026-06-29 19:31:49 +02:00
marcopan 790ac71284 feat(backend): spawnFor new/resume prompt mode; resume sends /riprendi-sessione 2026-06-29 12:38:26 +02:00
marcopan 387ae56583 test(backend): make mgr injectable; assert teardown-before-delete ordering 2026-06-29 12:36:29 +02:00
marcopanandClaude Opus 4.8 bd29ac517c feat(backend): rename/group/archive/unarchive/delete/documents routes + resume read-only guard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:32:52 +02:00
marcopan 4a63db51e8 feat(backend): ThtRunner session mutation + documents methods 2026-06-29 12:29:14 +02:00
marcopanandClaude Opus 4.8 f59d6d1478 fix(backend): allow PUT in CORS methods so browser can save settings
Found via live browser test: PUT /settings preflight was rejected because
@fastify/cors default methods omit PUT. GET/POST were unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:54:30 +02:00
marcopanandClaude Opus 4.8 ec36ee421a feat(backend): POST /sessions applies global settings, body is question-only
workspace/provider/model/thinking now come from getSettings() injected into
sessionRoutes; the request body supplies only question+name. Also teaches
fake_pi_rpc to respond to set_model and set_thinking_level RPC commands so
tests that pass real model settings don't hang.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:16:08 +02:00
marcopanandClaude Opus 4.8 098915515f fix(backend): deterministic /models fallback test + merge duplicate import
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:12:18 +02:00
marcopanandClaude Opus 4.8 bfbb017413 feat(backend): /settings GET+PUT, /models PiModel shape, app wiring
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:09:04 +02:00
marcopanandClaude Opus 4.8 c4b599ec00 feat(backend): ephemeral Pi model lister (get_available_models) with TTL cache
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:04:33 +02:00
marcopanandClaude Opus 4.8 6b2883a467 feat(backend): persistent settings store + settingsFile config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:01:22 +02:00
marcopanandClaude Opus 4.8 091a055bf0 fix(backend): bridge maps real Pi message_update->text_delta for FE streaming
Live end-to-end against real pi --mode rpc revealed Pi streams assistant text as
top-level message_update events whose nested assistantMessageEvent carries the
incremental delta — not the top-level text_delta the fake-pi-rpc emits. The bridge
now maps message_update(assistantMessageEvent.text_delta).delta -> FE text_delta,
so the chat shows the model's output during a real session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:03:13 +02:00
marcopanandClaude Opus 4.8 1ee692cdb1 fix(backend): tht --config is per-command, append after subcommand (not global -c)
Live end-to-end surfaced a 500 on every tht call: ThtRunner prepended
'-c <config>' before the subcommand, but tht has no global -c option
('No such option: -c'). --config/-c is a per-command option, so it must be
appended AFTER the subcommand. Extracted buildArgv() and fixed the unit test
that had codified the wrong (prepended) order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:52:39 +02:00
marcopanandClaude Sonnet 4.6 c63b2bd126 fix(backend): idempotent spawnFor + identity-checked exit + unified SSE re-emit shape
- spawnFor now tears down any existing runtime for the same session id before
  the cap check, so resume/respawn neither leaks the old child nor falsely hits
  maxPiProcesses
- exit handler is identity-checked (captures rt) so a stale child's late exit
  cannot evict a newer runtime
- SSE pending re-emit now sends the full ClientEvent shape
  { type: "ui_request", ui_request } to match hub.publish live events
- tests: same-id respawn replaces runtime (count 1); old child exit does not
  evict new runtime; sse-hub re-emit asserts unified shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:53:12 +02:00
marcopanandClaude Sonnet 4.6 b16c94e30b feat(backend): resume + venv PATH + end-to-end F1 smoke (fake-pi-rpc)
- Add PiProcessManager.resume(sessionId, tht): reads provider/model/thinking
  from tht.sessionShow() and calls spawnFor with those values
- Add POST /sessions/:id/resume route: calls mgr.resume then re-wires
  bridge.onClientEvent → hub.publish
- Confirm venv PATH already present in real spawn (no change needed)
- Add e2e test: POST /sessions → SSE receives ui_request via pendingWidget
  re-emit → POST /sessions/s1/response → 204 (all over real HTTP against
  fake-pi-rpc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:37:24 +02:00
marcopanandClaude Sonnet 4.6 d630cac3ab feat(task-10): SQL routes + /workspaces + /models + fix sql preview path bug
Harness:
- preview_cmd FILE positional arg made optional; when omitted with --session,
  path is derived via _session_sql_file (mirrors export_cmd) — fixes the
  deferred Task-5 bug where the backend passed sessions/<id>/sql_final.sql
  relative to harnessDir, which broke for workspace-dependent paths.
- New pytest: test_preview_session_no_file_resolves_sql_final

Backend:
- ThtRunner.sqlPreview: drop positional file arg; use --session only
- New routes/sql.ts: POST /sessions/:id/sql/preview + /export
- New routes/meta.ts: GET /workspaces (yaml scan) + GET /models (injectable
  seam + graceful fallback to {models:[]})
- app.ts: register sqlRoutes + metaRoutes; add listModels to BuildAppDeps
- tht-runner.test.ts: add sqlPreview argv assertion (no file path)
- test/routes-sql-meta.test.ts: 9 tests (sql preview/export + meta routes)

Tests: harness 233 passed; backend 29 passed; build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:31:35 +02:00
marcopanandClaude Sonnet 4.6 061002f85c fix(backend): forward workspace to tht config selection (MVP backend-side)
POST /sessions no longer silently drops `workspace`. ThtRunner.run/json
take an optional workspace; configArg() selects workspaces/<ws>.yaml when
it exists under harnessDir, else falls back to default configPath.
sessionNew threads workspace through. Route forwards b.workspace with an
MVP note (gate/Pi side still single-workspace via symlinked config/tht.yaml).

19/19 tests pass, tsc clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:26:09 +02:00
marcopanandClaude Sonnet 4.6 b4b26e313b feat(backend): session routes + SSE + response/steer wiring
Make buildApp injectable (deps.thtRunner + deps.spawnFn); create
src/routes/sessions.ts with all 7 session routes under authPreHandler;
wire bridge.onClientEvent→hub.publish before returning {id} from POST
/sessions; SSE subscribes with pendingWidget safety net.

18/18 tests pass, tsc clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:19:19 +02:00
marcopanandClaude Opus 4.8 a58fb19340 feat(backend): pluggable auth (none/mock/oidc seam)
- authPreHandler(mode) returns Fastify preHandler that sets req.user={id}
- none: uses dev@local
- mock: reads x-mock-user header
- oidc: MVP stub returns 501 + throws
- getUser(req) returns user object

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:13:26 +02:00
marcopan 39eb35116e feat(backend): SSE hub with pending-widget re-emit on (re)subscribe 2026-06-27 21:10:48 +02:00
marcopanandClaude Opus 4.8 f4c6126270 refactor(backend): drop dead SpawnFn alias + test Pi exit-handler cleanup
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:09:49 +02:00
marcopanandClaude Sonnet 4.6 de7200f7dd feat(backend): PiProcessManager (one Pi per session, cap, set_model/thinking)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 21:05:03 +02:00
marcopanandClaude Sonnet 4.6 c680060bd9 feat(backend): ThtRunner wrapper for tht --json (sessions, sql preview/export)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 20:59:58 +02:00
marcopanandClaude Sonnet 4.6 2d680c958c feat(backend): SessionBridge widget-descriptor <-> RPC + pending widget
Decodes native extension_ui_request (method:input, title=JSON) into
ui_request ClientEvent; encodes respond() as extension_ui_response with
value payload; tracks pending widget; handles notify→info and steer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 20:56:26 +02:00
marcopan df5d2c36ac feat(backend): RpcClient (spawn/send/request/event) over JSONL 2026-06-27 20:52:22 +02:00
marcopanandClaude Opus 4.8 de56694433 feat(backend): LF-only JSONL line splitter
Implement attachJsonlReader to split streams on \n only, handling
trailing \r, and reassembling lines split across chunk boundaries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:49:00 +02:00
marcopanandClaude Sonnet 4.6 b09de85c6f feat(backend): scaffold Fastify+TS + /health
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 20:46:08 +02:00