Skip to content

Commit 50ba74a

Browse files
committed
Release v0.2.0-pre.3
1 parent 467c009 commit 50ba74a

2 files changed

Lines changed: 321 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 320 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,323 @@ section with a `⚠ breaking` prefix so they're easy to spot.
77

88
## [Unreleased]
99

10+
## [0.2.0-pre.3] — 2026-04-27
11+
12+
The "projects + Ask command surface" release. Sessions now belong to
13+
first-class projects, templates inherit project defaults with
14+
per-field overrides, and Ask becomes a full command line for the
15+
dashboard — searching, summarizing, launching, editing, and watching,
16+
all through one approval pipeline. No breaking changes.
17+
18+
### Added
19+
20+
#### Projects registry — first-class concept
21+
22+
- New `projects` table with name, cwd, optional GitHub URL, and
23+
default agentType / model / launchMode. Sessions get a nullable
24+
`project_id` FK that auto-resolves on event ingest via
25+
longest-prefix cwd match (path-segment-aware so `/foo/bar`
26+
doesn't match `/foo/barbaz`). An in-process cache loaded eagerly
27+
at boot keeps resolution off the DB hot path.
28+
- New `/projects` UI with create / edit / delete drawer, badges
29+
on `SessionCard` and `SessionDetailPage` header, and a Project
30+
filter on the dashboard. Endpoints: `GET/POST /api/v1/projects`,
31+
`GET/PUT/DELETE /api/v1/projects/:id`,
32+
`GET /api/v1/projects/:id/sessions`.
33+
- One-shot boot backfill stamps `project_id` on pre-existing
34+
sessions whose cwd matches an existing project — no manual
35+
re-resolution needed.
36+
37+
#### Template ↔ project linkage with live inheritance
38+
39+
- New `session_templates.project_id` FK + a
40+
`template_project_overrides` JSON sentinel so individual fields
41+
can be overridden without making `agentType` / `cwd` columns
42+
nullable. Project values flow live: change the project's
43+
`defaultAgentType` and every linked template renders the new
44+
value on next read. Override semantics: stored value wins where
45+
the user explicitly overrode, project value fills the rest.
46+
- Templates list endpoint resolves project values via a single
47+
IN-batch query so resolution stays O(1) extra queries no matter
48+
the list size.
49+
- Deleting a project nulls `session_templates.project_id` AND
50+
`sessions.project_id` AND removes the project row in one
51+
Drizzle transaction. A partial failure rolls all three back.
52+
- Auto-create-project on template save: if a template is saved
53+
without an explicit `projectId`, the server finds a project at
54+
the template's cwd or creates a new one (basename-derived name,
55+
numeric suffix on collision). The dropdown's first option now
56+
reads "Auto (match by directory)" to communicate the new
57+
default behavior.
58+
59+
#### AI Ask — read-only patterns (no approval, no mutation)
60+
61+
- **NL session search.** "show me failed sessions",
62+
"stuck sessions", "find sessions about auth on agentpulse" —
63+
heuristic keyword gate (no LLM), pure-synchronous filter
64+
derivation for status / time / project, FTS query with `mode:
65+
"or"` and a direct-query fallback when the user message is
66+
all filter words. Each hit is enriched with status + agentType
67+
via a single batched session lookup.
68+
- **Cross-cutting digest.** "what happened today",
69+
"give me a digest" — wraps the existing `buildDigest` service
70+
with a 5s `Promise.race` timeout and a "still loading" footer
71+
for instances with many live sessions whose intelligence
72+
classifiers are slow.
73+
- **Per-session Q&A.** "summarize session X", "why did session Y
74+
fail" — bounded transcript (10k-token tail-truncated, oldest
75+
events dropped, provenance footer in every reply), spend-cap
76+
preflight + postflight, response cache keyed on
77+
`(sessionId, sha256(normalizedQuestion))` invalidated by new
78+
events. New `ai_qa_cache` table; sweep purges expired rows.
79+
80+
#### AI Ask — mutations through `ai_action_requests` approval
81+
82+
- New `ai_action_requests` table with kinds: `launch_request`,
83+
`add_project`, `session_stop`, `session_archive`,
84+
`session_delete`, `edit_project`, `delete_project`,
85+
`edit_template`, `delete_template`, `add_channel`,
86+
`create_alert_rule`, `create_freeform_alert_rule`,
87+
`bulk_session_action`. Atomic claim via conditional UPDATE
88+
(`awaiting_reply → applying`) prevents double-execution on
89+
concurrent web + Telegram approvals; `applying → applied /
90+
failed / expired` lifecycle with `failure_reason`.
91+
- **AI-initiated session launches.** "open a Claude session for
92+
agentpulse" — keyword gate + LLM classifier resolve project
93+
and mode, validate against connected supervisors via pure
94+
helpers (`pickFirstCapableSupervisor`, `buildLaunchSpec`
95+
extracted from existing impure code), then create an
96+
approval card. On approve the executor re-validates the
97+
supervisor and dispatches through the existing `/launches`
98+
pipeline. Reroute path rebuilds the launch spec when the
99+
originally-validated host is gone.
100+
- **AI-driven add-project (multi-turn drafting).** "add a
101+
project myapp at /tmp/myapp" — new
102+
`ai_pending_project_drafts` table holds in-flight drafts
103+
keyed on `ask_thread_id`. The AI walks the user through
104+
numbered questions for missing fields one turn at a time;
105+
parsing each reply is pure synchronous so continuation
106+
turns make no LLM call. `cancel`/`abort`/`stop drafting`/
107+
`never mind` aborts a draft from any question; retry cap
108+
of 3 per field expires the draft cleanly.
109+
- **Quick session actions.** Pin / note / rename run direct
110+
with the resolved session name embedded in the reply for
111+
verification; stop / archive / delete go through approval.
112+
Notes append now (existing notes preserved with `\n`
113+
separator); rename replies include an explicit undo hint.
114+
Stop pre-flight rejects hook-only sessions before creating
115+
an action_request — `queueStopAction` only works on
116+
managed sessions.
117+
- **Resume / continue with a new prompt.** "continue
118+
brave-falcon with: refactor the auth module" — builds a new
119+
managed launch inheriting the parent session's cwd /
120+
agentType / model with the user's text as `taskPrompt`.
121+
Reuses the existing `launch_request` kind; the inbox card
122+
reads `payload.parentSessionId` and renders "Resume of
123+
*parentName*" when present so approvers see the resume
124+
context instead of a generic "New launch" title.
125+
- **Edit / delete project + template via Ask.** Four new
126+
action_request kinds. Delete cards include affected-template
127+
and affected-session counts so the approver sees the blast
128+
radius. Project deletion still uses the transactional
129+
cleanup so linked templates and sessions are nulled
130+
atomically.
131+
- **Notification channel setup via Ask.** "set up a Telegram
132+
channel called personal" — heuristic kind detection
133+
(`telegram` / `webhook` / `email`); the executor calls
134+
`createPendingChannel` and sends per-kind enrollment
135+
instructions back through `notifyOriginUser`.
136+
- **Bulk session operations.** "archive all completed
137+
sessions on agentpulse" — classifier picks one of two
138+
resolution strategies (attribute-based SQL or hint-based
139+
FTS). Pre-flight excludes incompatible targets per action
140+
(stop excludes hook-only; delete excludes active sessions);
141+
cap at 50 targets, 20-name preview with "+N more" footer.
142+
Per-target try/catch keeps a single failure from poisoning
143+
the rest of the batch; outcome summary message reports
144+
per-target results.
145+
146+
#### Project-level watcher alert rules
147+
148+
- New `project_alert_rules` table with `REFERENCES projects(id)
149+
ON DELETE CASCADE`, plus `project_alert_rule_fires` for
150+
de-bounce. Rule types: `status_failed`, `status_completed`,
151+
`status_stuck`, `no_activity_minutes`, `freeform_match`.
152+
`WatcherRunner` gains a 60-second sweep with re-entry guard
153+
(`alertSweepBusy` flag matching the `RunLeaser` precedent);
154+
evaluation extracted to `alert-rule-evaluator.ts`.
155+
- **First-run backfill** at rule creation inserts fire rows for
156+
every session that already matches the rule's predicate, with
157+
no notification dispatched. Without this, a freshly-created
158+
`status_stuck` rule on a project with thirty already-stuck
159+
sessions would notification-storm the user.
160+
- **Freeform watcher rules.** Natural-language conditions like
161+
"alert when the agent mentions a security concern" run a small
162+
yes/no LLM classifier per qualifying event. Per-rule daily
163+
token budget stored on the rule row, atomic daily reset via
164+
SQL `CASE` so a process restart can't read a stale zero,
165+
per-rule `last_evaluated_event_id` cursor + 100-event-per-sweep
166+
cap so a backlog can't blow the budget in one tick. Sample rate
167+
with cursor advance before sampling so 0.5 still bounds work.
168+
Spend recorded only on successful classification.
169+
170+
#### Search highlight + event-context
171+
172+
- Search-result event hits now scroll the activity timeline to
173+
the matching event and apply a 2.2s amber flash. A `useRef`
174+
guard ensures the flash fires exactly once per
175+
`(sessionId, eventId)` pair even as new events stream in via
176+
WebSocket; the ref also marks 404 / network failures as
177+
terminal so the effect can't loop on deleted events.
178+
- New `GET /api/v1/sessions/:sessionId/events/:eventId/context?around=N`
179+
endpoint returns the target event ± a window (default 20, max
180+
100). Used by the frontend to splice older events into the
181+
timeline state when the search hit references an event outside
182+
the loaded window.
183+
184+
#### Telemetry classification + diagnostics
185+
186+
- Telemetry pings now include an `install_class` field
187+
(`production` / `self_hosted_real` / `dev` / `test` / `ci`)
188+
inferred from build channel, with explicit overrides via
189+
`AGENTPULSE_TELEMETRY_MODE` and `AGENTPULSE_TELEMETRY_TEST=1`.
190+
Local and CI runs no longer pollute real-world install counts.
191+
- Added a `first_boot` vs `heartbeat` event_kind so the homepage
192+
adoption number can show distinct installs vs activity.
193+
- New `GET /api/v1/settings/telemetry/status` returns last-attempt
194+
diagnostics; `POST /api/v1/settings/telemetry/ping` triggers an
195+
immediate send. Both gated by `requireAuth`.
196+
197+
### Changed
198+
199+
- `resolveActionRequest` dispatches via a `KIND_EXECUTORS`
200+
registry object instead of an if-chain. Each new action kind is
201+
a one-line registry entry; an unsupported kind fails cleanly
202+
with `Unsupported action kind: <kind>`.
203+
- Inbox card rendering split into per-kind components under
204+
`src/web/components/inbox/`. The dispatch is a single
205+
exhaustive `switch` on `item.kind` so TypeScript flags any
206+
missing case at compile time.
207+
- `decideActionRequest` route now labels failures by action kind
208+
("Project edit failed: …", "Bulk session action failed: …",
209+
"Freeform alert rule failed: …") instead of always saying
210+
"Launch failed: …". The 422 / 409 split distinguishes terminal
211+
failure during this approval (expired / failed) from a real
212+
race-lost (another approval claimed first).
213+
- `evaluateAlertRules` extracted from `event-processor.ts` into a
214+
dedicated `alert-rule-evaluator.ts` so the four rule-type
215+
evaluators share one home with the shared
216+
`dispatchAlertRuleNotification` helper.
217+
- `resolveSession` extracted from `ask-session-action-handler.ts`
218+
to a shared `ask-resolver.ts` so Slice B's Q&A handler and
219+
Slice C's bulk handler can use the same FTS-backed
220+
ambiguity-protocol session picker without depending on the
221+
session-action handler.
222+
- `sendTelegramActionRequest` extracted from
223+
`ask-launch-handler.ts` into `telegram-helpers.ts` since four
224+
handlers now need it.
225+
- `updateTemplate` and `deleteTemplate` extracted from inline
226+
route logic into a `templates-service.ts` module so the new
227+
edit / delete executors can call service functions instead of
228+
duplicating route logic.
229+
- Ask `runAskTurn` chain now processes intent gates in this
230+
order: open-draft continuation → digest gate → search gate →
231+
add-project gate → session-action gate → resume gate → CRUD
232+
gate → channel gate → alert-rule gate → bulk gate → launch
233+
gate → normal LLM completion. Multi-turn drafting always wins
234+
over a fresh intent on the same thread.
235+
- `SearchFilters.sessionStatus` now accepts `"failed"`. Closes a
236+
previously-undocumented gap where `failed` was a valid
237+
`Session.status` value but couldn't be filtered against in
238+
the search UI or NL search resolver.
239+
- `sessions` table gains a `is_archived` boolean column,
240+
orthogonal to `status` so a failed or completed session can be
241+
archived without losing its terminal status. The new
242+
`PUT /api/v1/sessions/:id/archive` route flips this flag; the
243+
CLAUDE.md route reference is now backed by an actual handler.
244+
- `launch_requests` table gains a nullable `parent_session_id`
245+
column for traceability of resume launches. The launch
246+
pipeline does not read it; future "session tree" UI will.
247+
- `CreateActionRequestInput.kind` widened to the full eleven
248+
kinds the plan introduces, in one schema-less union edit, so
249+
each new slice's executor branch fails compilation cleanly
250+
until its handler lands.
251+
- `notes` semantics for the AI's add-note path are now append
252+
(read existing, concat with `\n`, write back) so the AI can't
253+
silently destroy prior notes. The direct
254+
`PUT /sessions/:id/notes` route is unchanged (full replace);
255+
this only differs in the `add_note` Ask path.
256+
- Local OpenAI-compatible providers (Ollama, vLLM, llama.cpp)
257+
now receive `reasoning_effort: "none"` on classifier calls so
258+
qwen3 and similar reasoning models return clean JSON instead
259+
of burying the response in chain-of-thought. The existing
260+
`think: false` and `chat_template_kwargs.enable_thinking:
261+
false` flags were silently dropped by Ollama's
262+
`/v1/chat/completions` endpoint — kept for back-compat but
263+
`reasoning_effort` is what does the work. Anthropic / OpenAI /
264+
Google / OpenRouter providers receive the prompt unchanged.
265+
266+
### Fixed
267+
268+
- **Default-projectId stamping race.** `bumpVersion()` originally
269+
fired the cache reload as `void reloadCache()` — non-blocking.
270+
`createProject` immediately fed `getCachedProjects()` into
271+
`resolveAllSessionsForProject`, so a freshly-created project
272+
could miss its own session-stamp pass and leave matching
273+
sessions unstamped until the next event-ingest. Now
274+
`bumpVersionAndReload` awaits the reload before returning.
275+
- **Search-highlight context fetch could loop on deleted events.**
276+
When the target event id no longer existed (`404` from the
277+
context endpoint), the effect's `loadingContext` flip retriggered
278+
the same fetch on the next render — infinite 404s. The catch
279+
branch now marks `(sessionId, eventId)` as terminal in
280+
`flashedRef` so the early-return chain short-circuits the
281+
effect on subsequent re-runs.
282+
- **Misleading "race_lost" message** when a `/decide` call's own
283+
approval transitioned the action_request to `expired` or
284+
`failed` during execution. The route conflated genuine race
285+
losses with terminal-during-this-attempt outcomes. The resolver
286+
now returns a discriminated `ResolveResult` and the route
287+
branches on `reason` — race-lost is 409, terminal failure is
288+
422 with the real reason.
289+
- **`sessions.status = "failed"` was never written.** The value
290+
existed in the type union and schema comment but had no
291+
producer in the codebase. Launch dispatch now invokes
292+
`markSessionFailed` when a launch transitions to failed —
293+
required before the `status_failed` alert rule could fire on
294+
anything.
295+
- **Periodic alert-rule sweep had no re-entry guard.** A slow
296+
sweep (50 sessions × Telegram round-trip) overlapping with
297+
the next 60s tick could produce two concurrent Telegram
298+
messages for the same rule/session before the UNIQUE
299+
constraint stopped the second DB insert. Added an
300+
`alertSweepBusy` flag matching the `RunLeaser` precedent.
301+
- **`no_activity_minutes` filter missed idle-but-not-stopped
302+
sessions.** Original spec used `isWorking = true` which
303+
doesn't catch sessions that emitted `Stop` but haven't
304+
started a new task. Filter now uses `endedAt IS NULL`.
305+
- **Daily token-budget reset for freeform rules was a
306+
read-modify-write race.** A process restart mid-day could
307+
re-read a stale `0` from the previous reset and classify
308+
events that should have been blocked. Reset is now an atomic
309+
SQL `CASE` UPDATE per row so concurrent processes can't
310+
diverge.
311+
- **Spend counter incremented on LLM errors.** Freeform-rule
312+
classification now records spend only on successful
313+
classification — `classifyFreeformCondition` returns a
314+
discriminated `ClassifyResult` and the caller skips spend
315+
recording on the error path.
316+
- **Telegram approve-callback identifier mismatch fixed in the
317+
add-project flow.** Action_requests now persist
318+
`notification_channels.id` (UUID) on the `channelId` column,
319+
not the raw Telegram chat id; inbound callbacks look up the
320+
channel by chat id and match on the persisted UUID — same
321+
pattern HITL already uses.
322+
- **Lint formatter drift on telemetry classification commit**
323+
fixed before the merge so the project's `bun run check`
324+
stays at zero errors.
325+
326+
10327
## [0.2.0-pre.2] — 2026-04-25
11328

12329
The "find any past conversation" release. Three new layers stack on
@@ -459,6 +776,8 @@ All AI features ship gated behind per-feature Labs flags and a master
459776
- Local install: `docker run -d -p 3000:3000 -v agentpulse-data:/app/data -e DISABLE_AUTH=true`.
460777
- Remote hook relay: `curl -sSL https://server/setup-relay.sh | bash -s -- --key ap_xxx`.
461778

462-
[Unreleased]: https://github.com/jstuart0/agentpulse/compare/v0.2.0-pre.1...HEAD
779+
[Unreleased]: https://github.com/jstuart0/agentpulse/compare/v0.2.0-pre.3...HEAD
780+
[0.2.0-pre.3]: https://github.com/jstuart0/agentpulse/releases/tag/v0.2.0-pre.3
781+
[0.2.0-pre.2]: https://github.com/jstuart0/agentpulse/releases/tag/v0.2.0-pre.2
463782
[0.2.0-pre.1]: https://github.com/jstuart0/agentpulse/releases/tag/v0.2.0-pre.1
464783
[0.1.0]: https://github.com/jstuart0/agentpulse/releases/tag/v0.1.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agentpulse",
3-
"version": "0.2.0-pre.2",
3+
"version": "0.2.0-pre.3",
44
"description": "Command center for AI coding agents across all your machines",
55
"type": "module",
66
"bin": {

0 commit comments

Comments
 (0)