Commit Graph

3806 Commits

Author SHA1 Message Date
Émile Ré
9e4cf09e44 Fix turbo lint
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 18:39:58 +04:00
Émile Ré
535b7a6052 Relax package.json engines versions
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 17:15:46 +04:00
Émile Ré
c88c6ba8f6 Fix lint
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:06:45 +04:00
Émile Ré
5988070a42 Add --state and --contract-ended flags to CLI user list
Replace the --active boolean flag with two explicit filters:
--state (ACTIVE/INACTIVE) and --contract-ended (true/false).
Also add state filter support to the console GraphQL API.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:06:45 +04:00
Émile Ré
272f63828f Add state to MCP profile and refactor contract filter
Add the profile state attribute (ACTIVE/INACTIVE) to the MCP
Profile schema so listUsers and getUser tools expose it, and
add a state filter to listUsers.

Rename excludeContractEnded to contractEnded across the entire
stack (MCP, GraphQL, CLI, frontend). The new boolean is two-way:
true returns only users with ended contracts, false returns only
users with active or no contract, and null returns all.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:06:44 +04:00
Émile Ré
b7a28573f7 Send SDK version as X-SDK-Version header
Move the cookie-banner SDK version from the POST consents
request body to a custom X-SDK-Version header sent on every
API call. The server now reads it from the header and the
CORS middleware allows it through preflight.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:03:03 +04:00
Émile Ré
abafd817a8 Bump @probo/cookie-banner to 0.0.4
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:06 +04:00
Émile Ré
7309d7fe76 Fix cookie labels color
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:06 +04:00
Émile Ré
e4ae05c133 Update readme
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:06 +04:00
Émile Ré
79c17ad3a0 Update default exports field for main entrypoint and bump to 0.0.3
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:06 +04:00
Émile Ré
181a790061 Fix long cookie names display
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:05 +04:00
Émile Ré
9d0eff6c66 Fix long cookie names display
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:02:05 +04:00
Aurélien Sibiril
c43445d487 Add Notion workspace name resolver
Notion was the only wired access-review connector without a name
resolver, so the source kept the generic "Notion" placeholder. Fetch
the workspace name from /v1/users/me (bot.workspace_name) following
the same pattern as the other resolvers, and refresh the stale scope
comment now that Notion participates in name resolution.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-27 10:49:22 +02:00
Aurélien Sibiril
7934c54f42 Request Google customer directory scope for name sync
The access-review source-name worker calls Customers.Get("my_customer")
on the Google Admin SDK to resolve the Google Workspace primary domain.
That endpoint requires admin.directory.customer.readonly; without it
the request returns 403 and the source keeps the generic placeholder
name. The scope is already requested by the SCIM bridge -- align the
access-review driver with it.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-27 10:49:22 +02:00
Bryan Frimin
3bb2ede585 Style
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-27 10:30:14 +02:00
Aurélien Sibiril
e5e722ea83 Skip parallel suspension when checkpoint is nil
The triggering entry in the parallel-suspend path stored
se.Checkpoint into innerCheckpoints unconditionally, while
the sibling loop already guarded otherSE.Checkpoint != nil.
A nil entry would later cause restoreNestedSuspended to
dereference innerCP.AgentName and panic. Apply the same
guard so a malformed SuspendedError falls through to the
regular result-collection path instead of poisoning the
checkpoint map.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 23:21:50 +02:00
Aurélien Sibiril
fa56334c1f Add OnRunSnapshot hook for checkpoint persistence
Run hooks already exposed OnRunRestore for the read side of a
suspend/restore cycle. The write side -- every coreLoop or restore
call that persists a checkpoint to the Checkpointer -- had no
corresponding hook, so callers wanting to record metrics, audit
events, or trigger external state transitions on every snapshot had
no insertion point.

Add OnRunSnapshot to RunHooks and emit it after each successful
Checkpointer.Save: the suspend, awaiting-approval, nested-approval,
post-tool-turn, and restore-progress sites. The hook fires only on
durable saves; save failures still log and skip the hook so observers
never see a checkpoint that did not land.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
bfa1c21723 Document AgentRun.Update checkpoint write split
Update writes every mutable column of agent_runs except checkpoint.
That exclusion is intentional: PGCheckpointer.Save and ClearCheckpoint
are the only paths that touch the column, so a status commit cannot
overwrite an in-flight checkpoint saved between Load and Update.

Surface the rule on the Update method so future readers do not patch
in a checkpoint write thinking it was an oversight.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
119638deda Skip LastAgent in Result JSON serialization
Result.LastAgent is *Agent, which holds only unexported fields. The
default json.Marshal renders it as an empty object, which is misleading
when persisted alongside the agent run.

Tag LastAgent as json:"-" and add explicit lowercase JSON tags to the
remaining fields so the serialized shape is stable for callers that
read the persisted result column.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
e5b354c3b4 Collapse RunWithOpts duplicates into variadic options
Run, RunStreamed, and Resume each shipped both a no-options form and a
mirror *WithOpts form taking variadic RunOption. Variadic parameters
are backward-compatible additions, so the wrappers were dead surface.

Make Run, RunStreamed, and Resume directly variadic and update the two
internal callers.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
feeb6496b9 Polish supervisor integration tests
Documents why the supervisor suite stays sequential (claim is
cross-tenant via LoadNextPendingForUpdateSkipLocked; parallel
supervisors would steal each other's runs) and switches the SIGTERM
subtest to errors.AsType[*exec.ExitError] to match the project
convention enforced elsewhere in this file.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
20dbc28398 Expose supervisor shutdown broadcast for tests
Adds AgentRunSupervisor.ShutdownBroadcast() returning the handler's
shutdown channel so the StopAndResume integration test can wait for
graceful-shutdown propagation deterministically instead of sleeping
for a fixed duration. The method is explicitly documented as
test-only and not part of the operational contract.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
6e3de55173 Truncate persisted agent run errors on rune boundary
Raw tool or LLM errors can embed URLs with credentials, PII, or
partial DB records. We now log the full runErr for operator context
and persist a sanitized summary into agent_runs.error_message,
truncated at 512 bytes with a trailing ellipsis. The cut rewinds to
the nearest utf8.RuneStart so we never store a split rune. Also
wraps the pending-run loader error in Claim for parity with the
other coredata failures.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
cc579b1ed4 Parallelize PG checkpointer subtests
Each subtest now inserts its own PENDING run and runs under
t.Parallel(); shared state across subtests was the only reason they
had to stay sequential. Also adds a round-trip test that exercises
the approval-state fields (PendingToolCalls, PendingApprovals,
ApprovalInput, AllToolCalls, InnerCheckpoints, CompletedCalls) to
catch regressions where Save/Load drops nested or approval payloads.
The nonexistent-run case now uses a valid GID in the same tenant so
it reaches the row-not-found branch instead of short-circuiting on
the tenant-scope guard.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:22 +02:00
Aurélien Sibiril
33119f9306 Scope PGCheckpointer queries by tenant GID
Each Save and Load now derives tenant_id from the run GID and pins it
in the WHERE clause. A caller that supplies an ID from another tenant
fails closed instead of silently reading or overwriting cross-tenant
checkpoint data. Also rejects oversize checkpoints on load as a
read-side guard against a tampered or migrated row exceeding
MaxCheckpointBytes.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
70139a9210 Drop unused AgentRun.SaveCheckpoint
The transactional variant is no longer reachable: supervisor paths
persist checkpoints through PGCheckpointer (WithConn, not WithTx),
and Update deliberately excludes the checkpoint column to avoid
racing a concurrent checkpoint save. Nothing else calls it.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
3cebf7a1df Refine tool dispatch errors in resume path
Prefix the unknown-tool error with the "cannot" convention and drop
the duplicate wrap around executeSingleTool: that helper already wraps
its generic error path, so the outer wrap produced messages shaped like
"cannot execute tool X: cannot execute tool X: ...".

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
d13c83c19f Snapshot MaxTurns in checkpoint and apply it on restore
MaxTurns is the only agent bound compared against a counter that is
serialised in the checkpoint (Turns). When config drifts between save
and restore -- typically because a deploy changed WithMaxTurns or a
different build of the agent is registered by name -- cp.Turns can
exceed agent.maxTurns on the resumed run, which previously surfaced
as a warning log and then a MaxTurnsExceededError on the first
iteration of the resumed coreLoop.

Capture MaxTurns in the new AgentConfig on every save, and on
restore clone the registry-resolved agent with WithMaxTurns applied
from the snapshot. The override flows through the outer Restore path
and through both inner-agent resolution sites in
restoreNestedSuspended and restoreAwaitingApproval, so nested
runs get the same treatment. Other loop bounds
(maxEmptyOutputRetries, maxToolDepth) reset per turn / per tool
depth and stay intentionally live so deploys can tune them without
invalidating in-flight checkpoints. Live references (tools, hooks,
LLM client, approval callbacks, guardrails) are not snapshotted for
the same reason.

With the snapshot in place, the "restored agent run has already
reached max turns" warning at the top of continueFromMessages is
structurally unreachable -- the live agent's bound is now the same
value cp.Turns was bounded by at save time -- and is removed.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
6887294c9e Rebuild AgentRunSupervisor on go.gearno.de/kit/worker
The supervisor was a hand-rolled polling, semaphore, and wait-group
loop predating the project's adoption of the shared worker kit. Two
sibling workers in pkg/probo already use the kit, and go-worker.md
documents it as the project convention.

This commit introduces agentRunHandler, which implements
worker.Handler[coredata.AgentRun] and worker.StaleRecoverer, and
reduces AgentRunSupervisor to a thin wrapper that owns the handler
plus a worker.Worker and bridges ctx cancellation into a handler-
level shutdown broadcast via context.AfterFunc. The agent stop
channel is now closed by a per-Process forwarder goroutine when the
broadcast fires, so in-flight runs checkpoint at the next turn
boundary and drain through wg.Wait before Run returns.

The stop_requested column, struct field, supporting SQL, and the
LoadRunningStopRequestedIDs function are removed end-to-end. None
of it was ever wired to an external surface; it existed purely to
let the supervisor find runs the operator wanted to halt. With the
kit handling the polling cadence and the AfterFunc bridging
shutdown, per-row flagging is dead weight.

The supervisor's public API (NewAgentRunSupervisor, Run, the With*
option helpers, and the error sentinels) stays intact so probod.go
needs no change. The integration test now triggers stop by
cancelling the supervisor context, which is the actual production
path through SIGTERM rather than a synthetic DB flag. Prometheus
counters and OTel spans labelled worker="agent-run-supervisor"
come for free.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
fb88318c54 Fix suspension checkpoint fallback in nested and parallel execution
executeParallel ignored SuspendedError when checkpoint was nil,
treating it as a normal tool error. Nested suspension propagation
also dropped the in-memory checkpoint when persistence failed,
making runs non-resumable.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
83dba57add Move supervisor test to agentruntest package
The test does not belong in the probo package. Move it
alongside its shared helpers in pkg/agentruntest.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
71e2d00b3a Rename CheckpointStatus to AgentStatus
The status values describe the agent state, not the
checkpoint data state.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
ff18a5fc23 Remove json tags from internal structs
JSON marshaling uses field names directly; explicit tags
are unnecessary at this level.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
7058d310b0 Add agent run supervisor integration tests
Tests the checkpoint persistence and supervisor lifecycle against a
real Postgres database:

- PGCheckpointStore Save/Load/Delete round-trip
- Supervisor claims PENDING run and completes it
- Cooperative stop/resume via stop_requested flag
- SIGTERM battle test: 3 kill/resume cycles across 10 tool-call
  turns with parallel calls, long-running tools, thinking text,
  and progressive checkpoint accumulation

Tests skip gracefully when Postgres is unavailable.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
e831ee3c63 Add agent run supervisor worker
Poll-based supervisor that claims PENDING agent runs with FOR UPDATE
SKIP LOCKED, runs them with lease-based heartbeat, and handles
graceful shutdown. On infrastructure stop the row stays RUNNING so
stale recovery resets it to PENDING on restart; Restore picks up
from the last checkpoint. Heartbeat loss cancels execution without
committing a terminal status.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
2f2b8fe6aa Add AgentRun entity and PG checkpoint store
Create agent_runs table with lease-based concurrency control.
AgentRun entity follows standard coredata patterns with Scoper,
StrictNamedArgs, and cursor pagination. PGCheckpointStore implements
agent.CheckpointStore backed by the checkpoint JSONB column with
version validation and 10 MiB size guard. Register AgentRunEntityType
as entity type 75.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
363d696906 Add Restore function for agent checkpoint recovery
Restore loads a checkpoint from the store, resolves the agent from
a registry, and re-enters coreLoop. Handles suspended, nested
suspended (concurrent inner restore), and awaiting-approval states.
Partial progress is saved when some inner agents complete while
others remain suspended.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
7d5b933aa4 Add checkpoint persistence to agent core loop
coreLoop now saves incremental checkpoints after each tool-call turn
and checks a cooperative stop signal at turn boundaries. SuspendedError
is handled in finishRun, executeParallel, and executeSingleTool.
Approval-interrupted checkpoints are persisted for both flat and
nested interruptions.

Introduce RunOption, WithCheckpointStore, RunWithOpts, ResumeWithOpts,
and RunStreamedWithOpts so callers can provide checkpoint storage.
Add StreamEventSuspended and OnRunRestore hook.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
51df618c93 Add checkpoint types and stop signal for agent suspension
Introduce Checkpoint, CheckpointStore, SuspendedError, AgentRegistry,
and CompletedCall types. Add cooperative stop signal via context.
Export CompletedCall (was unexported completedCall) so checkpoints
can reference completed tool results. Add JSON tags to ToolResult
and ApprovalResult for checkpoint serialization.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Aurélien Sibiril
19a2610056 Add JSON serialization to LLM message types
Message, Part (Text/Image/File), ToolCall, FunctionCall, and Usage
now round-trip through JSON. Message uses a type-discriminated
envelope for the Part interface. Required for checkpoint persistence.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-04-26 22:58:21 +02:00
Émile Ré
d955489d11 Bump @probo/cookie-banner version
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 19:28:48 +04:00
Émile Ré
011057f52a Release v0.173.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 19:24:28 +04:00
Bryan Frimin
ad2ae1b20b Fix sandbox
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-24 17:18:25 +02:00
Émile Ré
dd42affd98 Drop SQL column defaults after migration for cookie_policy_url and sdk_version
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 19:01:27 +04:00
Émile Ré
56a32fe162 Fix intrusive auto-focus on cookie banner initial load
Only focus the banner on user-initiated re-opens (hidden -> banner),
not on the initial page load (loading -> banner). Also use
preventScroll to avoid jarring scroll jumps on programmatic focus.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:55:34 +04:00
Émile Ré
351cd49162 Add probo_consent cookie to necessary category on banner creation
The consent cookie is automatically created as a database record in
the necessary category when a new cookie banner is set up, so it
appears alongside other necessary cookies in the banner UI.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:40 +04:00
Émile Ré
d7eec08cd2 Add banner ID to probo_consent cookie
Include a `bid` field in the consent cookie so it explicitly
identifies which cookie banner it belongs to, making validation
direct instead of relying on the visitor ID as an implicit
discriminator. Existing cookies without `bid` self-heal on the
next load by falling through to the API fetch.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:40 +04:00
Émile Ré
d9db93911b Add cookie banner deletion from overview page
Wire the existing deleteCookieBanner GraphQL mutation to the
cookie banners overview list. Inactive banners with delete
permission show an action dropdown with a confirmed delete flow.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:40 +04:00
Émile Ré
a1d2eebf2b Hide cookie details toggle for categories with no cookies
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:39 +04:00
Émile Ré
598c6b112c Add cookie_policy_url field to cookie banners
Introduce a required cookie_policy_url alongside the existing
privacy_policy_url (now optional) so banners can link directly to a
dedicated cookie policy — a compliance best practice recommended by
CNIL, ICO, and the EDPB. Existing rows are seeded from their current
privacy_policy_url value.

Both {{cookie_policy_link}} and {{privacy_policy_link}} placeholders
are supported independently in banner description translations.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:39 +04:00