Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Agent Tank usage history**: a unified usage chart on the Analytics page
plots every provider's session, weekly and per-model metrics over the last
24 hours to 14 days, with provider and metric toggles. Hovering (or tapping)
a metric in the sidebar Usage section opens a chart of that metric over its
last two periods: 10 hours for a 5-hour session, 14 days for a weekly limit.
History comes from an external Agent Tank 0.10+ daemon when it keeps one.
Otherwise ProPR records its own, in a new `agent_tank_usage_history` table
kept for 15 days, served by `GET /api/config/agent-tank/history`.
- **Agent Tank 0.10.0**: the bundled CLI moves to 0.10.0, and ProPR reads its
canonical `{ "providers": [...] }` response format as well as the 0.9.x map,
so an external daemon can be upgraded independently. Antigravity's separately
tracked **Gemini 4 Argon** quota appears in the sidebar under its own
*Gemini 4 Argon* group.

- **Epic merge progress**: when a child PR merges into an epic branch, ProPR
updates a single "x of y PRs merged" tracking comment on the epic PR in
place, and posts a one-time *Epic fully merged* confirmation comment once
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile.agent
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ RUN case "${VIBE_CLI_VERSION}" in \

FROM agent-base AS agent-tank-cli
# Local upstream fixes until the next Agent Tank release includes them.
COPY patches/agent-tank-0.9.11.patch /tmp/agent-tank.patch
COPY patches/agent-tank-0.10.0.patch /tmp/agent-tank.patch
# Pinned here, not in CI: this literal is part of the Dockerfile content that
# feeds the agent bundle content hash, so bumping it produces a new image tag.
# An env-only override in CI would NOT change the tag and would silently ship a
# different binary under an existing tag - do not do that.
ARG AGENT_TANK_CLI_VERSION=0.9.11
ARG AGENT_TANK_CLI_VERSION=0.10.0
USER root
# node-pty compiles against the toolchain already present in agent-base
# (build-essential + python3), which is why this needs no extra apt packages.
Expand Down Expand Up @@ -215,7 +215,7 @@ ARG CODEX_CLI_VERSION=0.160.0
ARG ANTIGRAVITY_CLI_VERSION=1.4.0
ARG OPENCODE_CLI_VERSION=1.18.31
ARG VIBE_CLI_VERSION=2.25.8
ARG AGENT_TANK_CLI_VERSION=0.9.11
ARG AGENT_TANK_CLI_VERSION=0.10.0

LABEL dev.propr.agent-bundle="true" \
dev.propr.agent.claude.version="${CLAUDE_CLI_VERSION}" \
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.app.prod
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ COPY --from=builder /build/docs/mcp.md /build/docs/mcp-coverage.md /build/docs/m
COPY Dockerfile.agent ./Dockerfile.agent
COPY scripts/agent-entrypoint.sh ./scripts/agent-entrypoint.sh
COPY scripts/agent-tank-runtime.mjs ./scripts/agent-tank-runtime.mjs
COPY patches/agent-tank-0.9.11.patch ./patches/agent-tank-0.9.11.patch
COPY patches/agent-tank-0.10.0.patch ./patches/agent-tank-0.10.0.patch
COPY scripts/claude-entrypoint.sh ./scripts/claude-entrypoint.sh
COPY scripts/codex-entrypoint.sh ./scripts/codex-entrypoint.sh
COPY scripts/antigravity-entrypoint.sh ./scripts/antigravity-entrypoint.sh
Expand Down
12 changes: 11 additions & 1 deletion docs/docs/operations/agent-tank.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ It is **not** an API-spend tracker. For pay-as-you-go API key billing or per-req
|---|---|---|
| Claude (`claude`) | Runs the CLI's `/usage` command, or the Anthropic OAuth usage API in `--claude-api` mode | Current session, weekly (all models), and per-model weekly windows (Sonnet/Opus/Haiku) |
| Codex (`codex`) | JSON-RPC `account/rateLimits/read`, falling back to `/status` | 5-hour session limit and weekly limit |
| Antigravity (`agy`) | Runs the CLI's `/usage` command | Per-model quota availability and reset windows |
| Antigravity (`agy`) | Runs the CLI's `/usage` command | Per-model and per-pool quota availability and reset windows, including the separately tracked **Gemini 4 Argon** weekly quota |

OpenCode and Vibe are not tracked: neither CLI exposes a subscription usage endpoint for Agent Tank to read.

Expand Down Expand Up @@ -140,6 +140,8 @@ Because this is a backend setting rather than a stack container, `propr tank` ta
## What You See Once Enabled

- **Sidebar usage bars.** A per-provider Usage section shows each provider's windows (Claude session/weekly, Codex 5-hour/weekly, Antigravity per-model) as color-coded bars with reset countdowns, refreshed every 60 seconds, with a manual refresh button.
- **Usage history overlay.** Hover a metric row in the sidebar (tap it on a touch screen) to see how that metric moved over its last two periods: a 5-hour session shows the last 10 hours, a weekly limit the last 14 days. A dashed marker separates the previous period from the current one.
- **Usage chart in Analytics.** The Analytics page plots every provider's metrics on one 0–100% usage chart over the last 24 hours to 14 days, with toggles to show only some providers or metrics, and a table view of each series' latest, lowest and highest value.
- **Per-call usage deltas.** Around each agent run ProPR snapshots usage before and after the call, computes the delta per metric, and stores it next to the [LLM Log](./metrics.md) entry. The task detail context strip shows a compact session/weekly delta chip for the run.
- **Agents cost gate.** Before an unattended [agent](../features/agents.md#cost-control) run (scheduled, API, CLI or MCP trigger, or an `auto` acting step) starts, ProPR compares the agent provider's session and weekly usage with the instance's pause threshold (`agent_run_usage_pause_percent`, default 90%). Over the threshold, the run is deferred until the session window resets or skipped. The **Run now** button on the Agents page warns first when the provider is over the threshold. The gate only reads the snapshot Agent Tank already has (at most an hour old) and never starts a refresh or a bundled container.
- **Capacity in your metrics.** Provider capacity pressure becomes a first-class signal alongside cost and cycle time — see [Metrics](./metrics.md).
Expand All @@ -148,6 +150,14 @@ In bundled mode a baseline must be fresh when acquired near the start of the cal

{/* SCREENSHOT PLACEHOLDER (P3 — needs a running Agent Tank instance; interim: the site's ui-agent-tank.png): Capture the sidebar Usage section with Agent Tank enabled, showing provider rows (for example Claude and Codex) with colored usage bars and percentages, and one provider expanded to show its session and weekly metrics. Requires a running Agent Tank instance configured in Settings. */}

### Where Usage History Comes From

ProPR records a point for every metric each time it reads Agent Tank (the sidebar, the 60-second usage watcher while anyone is connected, and the MCP usage tool), storing a changed value at once and an unchanged one at most every 10 minutes. It keeps 15 days, enough for two weekly periods. Bundled mode relies on this record alone, because its Agent Tank container is discarded after every run.

In external mode, ProPR prefers the daemon's own history when the daemon is Agent Tank 0.10 or newer with history enabled (`GET /history/:id` reports `store: "sqlite"`): the daemon keeps refreshing while no ProPR tab is open, so its history has fewer gaps. An older daemon, or one with `--no-history`, falls back to ProPR's record. Both use the same metric keys, so switching modes doesn't split a series.

ProPR reads both Agent Tank response formats: the 0.10 `{ "providers": [...] }` format and the 0.9.x map keyed by agent. An external daemon can be upgraded before or after ProPR. The bundled image ships Agent Tank 0.10.0.

## Best-Effort By Design

The integration never delays starting the model call. Returning its result may wait for the bounded post-call usage refresh. If Agent Tank is disabled, unreachable, slow, or — in bundled mode — the agent image is missing or the container fails:
Expand Down
2 changes: 1 addition & 1 deletion docs/operations/managed-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Audited from `Dockerfile.agent`, `agents/version/types.ts`, `configManagerAgents
| Antigravity 1.2.4 (release 6085322963025920) | `M/<id>/.gemini`, legacy explicit root → `.gemini-source`; entrypoint may copy selected auth into disposable `/tmp/propr-antigravity-home/.gemini` | In a mounted root: `antigravity-cli/log/*.log`, `antigravity-cli/brain/<conversation>/.system_generated/logs/transcript.jsonl`; managed stdout/stderr includes native-goal segments | `antigravity-cli/antigravity-oauth-token`, settings, arbitrary brain/project/cache contents excluded | Task/goal process. Disposable container-only files are not falsely reported as mounted history. No native rotation/reopen proven; mounted append history is measured but not exported without complete credential context. Existing transient transcript/UI handling remains unchanged. |
| OpenCode 1.18.31 | `M/<id>/.config/opencode`; sibling `M/<id>/.local/share/opencode` or explicit `HOST_OPENCODE_DATA_DIR` supplies credentials. Runtime uses disposable XDG data via `PROPR_EPHEMERAL_STATE=1` | Mounted data root `log/*.log` when present; managed stdout/stderr for every execution | `auth.json`, `account.json`, config excluded; mounted `opencode.db`/WAL/SHM measured/protected, never exported | Runtime native DB/logs can be container-only; no broad copy or SQLite live-file copy. Captured stream is the supported diagnostic history for disposable runs. |
| Vibe 2.25.8, Python 3.12 | Explicit `.vibe` source is read-only. Runtime: `VIBE_PROMPT_CACHE_DIR/propr-vibe-runtime/propr-vibe-home-*` assigned by worker → `/tmp/propr-vibe-home`, also `HOME`/`VIBE_HOME` | Assigned runtime `logs/session/<id>/messages.jsonl`, plus stdout/stderr; same allowlist in an explicit mounted source | config.toml, credentials.json, .env, prompt cache files, skills and secrets excluded | Task-local runtime; snapshot while active and on child close, before existing cleanup. Does not add or promise Vibe native-goal resumption. |
| agent-tank 0.9.11 | Auxiliary runtime package in agent image, not a sixth `AGENT_TYPES` provider | Its diagnostic messages on the owning provider's captured streams | No extra home scan | Shares execution lifetime and stream policy. |
| agent-tank 0.10.0 | Auxiliary runtime package in agent image, not a sixth `AGENT_TYPES` provider | Its diagnostic messages on the owning provider's captured streams | No extra home scan | Shares execution lifetime and stream policy. |
| Shared `/tmp/claude-logs` | Launcher/Claude bind mount, not an isolated per-agent home | **No directory-wide collection**; associated execution stream is already captured with known agent identity | Conversation/output JSON is used by existing UI/session mappings. Unknown ownership is reported as `ownership-unverified`; nothing is removed | Multiple task writers/watchers, including JSON snapshot rewrites; cannot prove tenant/task ownership or safe reopen from filenames alone. |

Only these exact structural allowlists are traversed. New provider versions/formats require review of fixtures and this table; unknown/unparseable records are dropped with gaps, not shipped as raw bytes. Missing optional directories are valid. Native state has no archive-retrieval dependency because this policy never prunes it. It does not assert a complete byte inventory of excluded, unrecognized, or container-only state.
Expand Down
2 changes: 1 addition & 1 deletion docs/operations/native-provider-log-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Image pins in `Dockerfile.agent` are the compatibility baseline. Producer source
| Antigravity 1.2.4 (release 6085322963025920) | Mounted `.gemini/antigravity-cli/log/*.log` diagnostics; `brain/<conversation>/.system_generated/logs/transcript.jsonl` session history. Entrypoint uses disposable container runtime too. Goals span invocations. | Mounted originals protected and counted by class. Container-only originals unmeasured. No rotation contract verified. |
| OpenCode 1.18.31 | Explicit source `.local/share/opencode`, runtime XDG data in `/tmp/propr-opencode-data/opencode`. Current writer appends `log/opencode.log`. DB/WAL/SHM hold session state. | **`--print-logs` still writes the file**, adding stderr. Older upstream `Log.init` fixtures that returned early on `print` do not match this binary. All originals protected. |
| Vibe 2.25.8, Python 3.12 | Assigned worker runtime under `VIBE_PROMPT_CACHE_DIR/propr-vibe-runtime/...`, mounted `/tmp/propr-vibe-home`; `logs/session/<id>/messages.jsonl` is conversation history. Source home is read-only. | Session bytes, not disposable debug bytes. Existing task runtime cleanup unchanged. No new native-goal resumption guarantee. Unregistered runtime roots unmeasured. |
| agent-tank 0.9.11 | Auxiliary package; stream diagnostics belong to the containing execution. | No extra directory traversal. |
| agent-tank 0.10.0 | Auxiliary package; stream diagnostics belong to the containing execution. | No extra directory traversal. |
| App-created `/tmp/claude-logs` | `issueJobCallbacks.ts` and `prCommentJobHelpers.ts` create conversation snapshots/output text for task/UI handling; launcher shares the bind mount. Multiple writers/readers and restart mappings exist. | Ownership cannot be derived from filenames, dates or completed status. Not scanned/pruned; shared-directory declarations rejected. Growth unbounded/unmeasured. |

Credentials/configuration are metadata-only categories: this inventory reads no contents. Known auth/account/credential JSON, config/settings, `.env`, and the Antigravity OAuth store remain unchanged. Unknown files are counted without content reads; unknown directories are not traversed and make inventory incomplete. Existing producer redaction/private export restrictions still apply.
Expand Down
30 changes: 28 additions & 2 deletions docs/static/openapi/propr-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ info:
name: Apache-2.0
identifier: Apache-2.0
x-route-coverage:
total: 304
total: 305
documented: 69
undocumented: 235
undocumented: 236
servers:
- url: "{origin}"
description: Your ProPR API origin, for example the value of `API_PUBLIC_URL`.
Expand Down Expand Up @@ -2214,6 +2214,32 @@ paths:
x-propr-permission: instance.manage_agents
x-undocumented: true
x-legacy-error: true
/api/config/agent-tank/history:
get:
operationId: getApiConfigAgentTankHistory
summary: GET /api/config/agent-tank/history
description: |-
This route is registered but not documented yet.

Requires the `instance.manage_agents` instance permission.
tags:
- Configuration
security:
- sessionCookie: []
- bearerAuth: []
responses:
"200":
description: Success. The response body is not documented yet.
"401":
$ref: "#/components/responses/LegacyUnauthorized"
"403":
$ref: "#/components/responses/LegacyForbidden"
default:
$ref: "#/components/responses/LegacyUnexpectedError"
x-propr-auth: member
x-propr-permission: instance.manage_agents
x-undocumented: true
x-legacy-error: true
/api/config/agent-tank/refresh:
post:
operationId: postApiConfigAgentTankRefresh
Expand Down
3 changes: 3 additions & 0 deletions packages/api/routeRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export function createManagementRouteEntries({
['post', '/api/config/agent-tank', requireManageAgents, configRoutes.postAgentTankSettings],
['get', '/api/config/agent-tank/status', requireManageAgents, configRoutes.getAgentTankStatus],
['get', '/api/config/agent-tank/usage', requireAgentTankUsageAccess, configRoutes.getAgentTankUsage],
// History is read from this installation's own records and daemon; there is
// no synthetic demo history, so the demo exception does not extend to it.
['get', '/api/config/agent-tank/history', requireManageAgents, configRoutes.getAgentTankHistory],
['post', '/api/config/agent-tank/refresh', requireManageAgents, configRoutes.postAgentTankRefresh],
['get', '/api/config/agent-tank/detect', requireManageAgents, configRoutes.getAgentTankDetect],
['get', '/api/config/preview-storage', requireManageSettings, visualPreviewAuthRoutes.getManagedStorageStatus],
Expand Down
1 change: 1 addition & 0 deletions packages/api/routes/configRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ export function createConfigRoutes(deps: ConfigRoutesDeps) {
triggerIndexing: indexingRoutes.triggerIndexing, triggerReindexAll: indexingRoutes.triggerReindexAll, stopIndexing: indexingRoutes.stopIndexing,
getAgentTankSettings: agentTankRoutes.getAgentTankSettings, postAgentTankSettings: agentTankRoutes.postAgentTankSettings,
getAgentTankStatus: agentTankRoutes.getAgentTankStatus, getAgentTankUsage: agentTankRoutes.getAgentTankUsage,
getAgentTankHistory: agentTankRoutes.getAgentTankHistory,
postAgentTankRefresh: agentTankRoutes.postAgentTankRefresh, getAgentTankDetect: agentTankRoutes.getAgentTankDetect
};
}
Loading
Loading