Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ef92288
Implementation complete. Summary of what I built for #2547:
Sep 26, 2026
2deac23
feat(ai): **Cause of the shard 4 failure**
Sep 26, 2026
f2f10e6
merge: resolve conflicts from main into 2547/claude-opus-5-bundle-age…
Sep 26, 2026
db58429
feat(ai): ## What failed
Sep 26, 2026
398dd90
merge: resolve conflicts from main into 2547/claude-opus-5-bundle-age…
Sep 26, 2026
ea4599e
feat(ai): Both selected findings are fixed, each with a regression th…
Sep 26, 2026
6309787
feat(ai): Applied F3 only.
Sep 27, 2026
8b74a88
feat(ai): Both selected findings are fixed, each with a regression th…
Sep 27, 2026
747b656
feat(ai): Both selected findings are fixed, each with regressions tha…
Sep 27, 2026
650233f
feat(ai): Both selected findings are fixed, each with a regression th…
Sep 27, 2026
779b996
feat(ai): Applied F10 only.
Sep 27, 2026
9f521f7
feat(ai): Both selected findings are fixed, each with regressions tha…
Sep 27, 2026
94bf8da
feat(ai): Both review suggestions are addressed.
Sep 27, 2026
adada30
merge: resolve conflicts from main into 2547/claude-opus-5-bundle-age…
Sep 27, 2026
cbd4de3
feat(ai): **Root cause**
Sep 27, 2026
f7f04f6
merge: resolve conflicts from main into 2547/claude-opus-5-bundle-age…
Sep 29, 2026
1b381ea
feat(ai): Fixed the dependency audit failure by updating vulnerable `…
Sep 29, 2026
e625322
feat(ai): Fixed a smoke-fixture race: successful `{}` responses could…
Sep 29, 2026
547e284
feat(ai): Fixed both API test failures by updating mode expectations …
Sep 29, 2026
6290009
feat(ai): Updated Agent Tank to **0.9.11** in both Dockerfile pins, t…
Sep 29, 2026
4b110cd
fix(agent-tank): isolate writable provider state from read-only crede…
proprdev Sep 29, 2026
84aa66d
feat(ai): Addressed F1 only: added the helper COPY to the production …
Sep 29, 2026
8b04403
Merge pull request #2623 from integry/fix/pr2555-live-usage
integry Sep 29, 2026
b8ba253
feat(ai): Implemented F13, F14, and F15 only.
Sep 29, 2026
fdd7b1d
feat(ai): Fixed F16 only. Blank or whitespace-only external URLs now …
Sep 29, 2026
0e64584
feat(ai): Fixed both reported failures:
Sep 29, 2026
e906287
feat(ai): Implemented F17 and F18 only.
Sep 30, 2026
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
9 changes: 9 additions & 0 deletions .github/workflows/docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,15 @@ jobs:
AGENT_TAG: ${{ env.DOCKERHUB_NS }}/agent:${{ steps.version.outputs.version }}
run: ./scripts/verify-antigravity-image.sh

# Bundled Agent Tank mode runs the CLI inside this image, so the release
# has to prove it returns real usage - not just that the binary is there.
# Antigravity is the provider this runner is authenticated for.
- name: Verify authenticated bundled Agent Tank usage from packaged agent image
env:
AGENT_TAG: ${{ env.DOCKERHUB_NS }}/agent:${{ steps.version.outputs.version }}
AGENT_TANK_PROVIDERS: agy
run: ./scripts/verify-agent-tank-image.sh

- name: Validate serialized SQLite startup with packaged images
env:
APP_TAG: ${{ env.DOCKERHUB_NS }}/app:${{ steps.version.outputs.version }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ jobs:
scripts/smoke-test-images.sh \
scripts/smoke-test-preview-runtime-images.sh \
scripts/smoke-test-sqlite-startup.sh \
scripts/verify-agent-tank-image.sh \
scripts/verify-antigravity-image.sh
} > >(tee -a build_log.txt) 2>&1

Expand Down
25 changes: 23 additions & 2 deletions Dockerfile.agent
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ RUN case "${VIBE_CLI_VERSION}" in \
&& if [ ! -e /usr/local/bin/vibe-acp ]; then ln -s /usr/local/bin/vibe /usr/local/bin/vibe-acp; fi


FROM agent-base AS agent-tank-cli
# 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
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.
RUN npm install -g "agent-tank@${AGENT_TANK_CLI_VERSION}" \
&& npm cache clean --force \
&& rm -rf /root/.npm \
&& agent-tank --version


FROM agent-base AS antigravity-cli
ARG ANTIGRAVITY_CLI_VERSION=1.2.4
ARG ANTIGRAVITY_CLI_RELEASE_ID=6085322963025920
Expand Down Expand Up @@ -196,18 +211,21 @@ ARG CODEX_CLI_VERSION=0.154.0
ARG ANTIGRAVITY_CLI_VERSION=1.2.4
ARG OPENCODE_CLI_VERSION=1.18.31
ARG VIBE_CLI_VERSION=2.25.4
ARG AGENT_TANK_CLI_VERSION=0.9.11

LABEL dev.propr.agent-bundle="true" \
dev.propr.agent.claude.version="${CLAUDE_CLI_VERSION}" \
dev.propr.agent.codex.version="${CODEX_CLI_VERSION}" \
dev.propr.agent.antigravity.version="${ANTIGRAVITY_CLI_VERSION}" \
dev.propr.agent.opencode.version="${OPENCODE_CLI_VERSION}" \
dev.propr.agent.vibe.version="${VIBE_CLI_VERSION}"
dev.propr.agent.vibe.version="${VIBE_CLI_VERSION}" \
dev.propr.agent-tank.version="${AGENT_TANK_CLI_VERSION}"

USER root
COPY --from=claude-cli /usr/local/lib/node_modules/@anthropic-ai /usr/local/lib/node_modules/@anthropic-ai
COPY --from=codex-cli /usr/local/lib/node_modules/@openai /usr/local/lib/node_modules/@openai
COPY --from=opencode-cli /usr/local/lib/node_modules/opencode-ai /usr/local/lib/node_modules/opencode-ai
COPY --from=agent-tank-cli /usr/local/lib/node_modules/agent-tank /usr/local/lib/node_modules/agent-tank
COPY --from=vibe-cli /usr/local/bin/uv /usr/local/bin/uv
COPY --from=vibe-cli /usr/local/bin/uvx /usr/local/bin/uvx
COPY --from=vibe-cli /usr/local/bin/vibe /usr/local/bin/vibe
Expand All @@ -223,6 +241,7 @@ ENV UV_TOOL_DIR=/opt/uv/tools \

COPY --chown=node:node \
scripts/agent-entrypoint.sh \
scripts/agent-tank-runtime.mjs \
scripts/claude-entrypoint.sh \
scripts/codex-entrypoint.sh \
scripts/antigravity-entrypoint.sh \
Expand All @@ -241,6 +260,7 @@ RUN set -eu; \
link_npm_bin @anthropic-ai/claude-code claude \
&& link_npm_bin @openai/codex codex \
&& link_npm_bin opencode-ai opencode \
&& link_npm_bin agent-tank agent-tank \
&& ln -sf /home/node/.local/bin/agy /usr/local/bin/agy \
&& chmod +x \
/home/node/agent-entrypoint.sh \
Expand All @@ -255,7 +275,8 @@ RUN set -eu; \
&& codex --version \
&& opencode --version \
&& vibe --version \
&& agy --version
&& agy --version \
&& agent-tank --version

USER node
RUN git config --global user.name "ProPR Agent Bot" \
Expand Down
8 changes: 6 additions & 2 deletions apps/desktop/scripts/packaged-connect-journey-fixture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,12 @@ export const createPackagedJourneyFixture = async ({ approvalReadinessDelayMs =
return;
}
if (request.method === 'GET' && record.authorization === `Bearer ${token}`) {
response.writeHead(200, cors);
response.end('{}');
// Optional dashboard reads can finish before the authenticated socket.
// Returning 200 with {} supplies invalid data that crashes the route and
// removes the selector before it can report REACT_CONNECTED. Let these
// unimplemented reads use the renderer's normal request-error handling.
response.writeHead(404, cors);
response.end('{"code":"UNIMPLEMENTED_SMOKE_ENDPOINT"}');
return;
}
} catch {
Expand Down
16 changes: 15 additions & 1 deletion apps/desktop/scripts/packaged-connect-journey.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const encryption = {
const confirmationPath = '/api/auth/user?desktop_account_confirmation=1';

for (const delay of [0, 300]) {
test(`real credential service pairs and reprobes against Connect fixture (approval delay ${delay}ms)`, async () => {
test(`real credential service pairs, rejects unimplemented API reads, and reprobes against Connect fixture (approval delay ${delay}ms)`, async () => {
const fixture = await createPackagedJourneyFixture({ approvalReadinessDelayMs: delay });
const directory = await mkdtemp(join(tmpdir(), 'propr-connect-account-test-'));
const profiles = new ProfileStore(directory, encryption);
Expand All @@ -43,6 +43,20 @@ for (const delay of [0, 300]) {
confirmation.assertComplete();
assert.deepEqual((await profiles.list()).profiles[0].account, PACKAGED_CONNECT_ACCOUNT);
assert.equal((await service.probe(profile)).status, 'ready');
// The connected renderer reads these before its socket is necessarily ready.
// A successful {} is invalid dashboard data and can unmount the entire route,
// preventing REACT_CONNECTED even though authentication succeeded.
for (const path of [
'/api/dashboard/active?repository=all',
'/api/dashboard/stats?repository=all&period=7d',
'/api/unimplemented-connect-smoke-endpoint',
]) {
const response = await fetch(`${fixture.endpoint}${path}`, {
headers: { Authorization: `Bearer ${fixture.secrets[2]}` },
});
assert.equal(response.status, 404, path);
assert.deepEqual(await response.json(), { code: 'UNIMPLEMENTED_SMOKE_ENDPOINT' });
}
await service.dispose();
await profiles.close();
const reloaded = new ProfileStore(directory, encryption);
Expand Down
1 change: 1 addition & 0 deletions docker/Dockerfile.app.prod
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ COPY --from=builder /build/packages/api/package.json ./packages/api/
# through the mounted Docker socket, using /usr/src/app as the Docker context.
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 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
5 changes: 5 additions & 0 deletions dockerhub/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ agent's credentials and task worktree. Version-specific bundle tags contain a
complete CLI version matrix, so every agent instance can switch to the same
image without another pull.

The image also bundles the [Agent Tank](https://github.com/integry/agent-tank)
CLI (`agent-tank`). ProPR's optional bundled usage-tracking mode runs it here
on demand, so operators do not have to install it — or a second copy of the
agent CLIs — on the host. It is inert unless that mode is enabled.

The common Debian runtime is an internal Dockerfile stage, not a separately
published image. Custom installation-level packages create one derivative of
the selected bundle. The base includes `build-essential` for native extension
Expand Down
20 changes: 18 additions & 2 deletions docs/ci-runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ expected contract, not a claim that the workers are configured or validated:
`DOCKER_CONTEXT`, `DOCKER_TLS_VERIFY` and `DOCKER_CERT_PATH`. Job setup replaces
HOME and Docker client config, so a saved HOME-based Docker context is not a
reliable endpoint. `ci-rootless-preflight.sh` rejects default/remote/production
endpoints, checks the daemon reports rootless, and requires cgroup v2/systemd.
endpoints, checks the daemon reports rootless, requires cgroup v2/systemd, and
requires an init binary (the Redis helper starts `--init` containers).
It does not prove socket ownership, host mount isolation or effective limits.
- CI paths used as Docker bind sources must contain the same files at the same
absolute path inside the runner and the daemon's host mount namespace. Map
Expand Down Expand Up @@ -323,10 +324,25 @@ older attempts matching the exact owner; it preserves newer attempts and all
other owners. An unexpected owner fails closed. Existing callers with no
instance, including nightly, retain one container per job and attempt.

Each container runs with `--init`. The container's PID namespace reparents every
health-check process to PID 1 once its runc parent exits, and `redis-server`
does not reap them; one check every two seconds for the length of a shard
therefore filled `--pids-limit` with zombies and left a container the rootless
daemon could not kill, which failed the teardown step of a shard whose tests had
all passed. tini as PID 1 reaps them instead.

Teardown (`stop`) is the only caller that tolerates a failed removal. It runs
after the tests have decided the job's result, and no step in the job can reap a
zombie PID, so a container whose ownership fully verifies but which the daemon
still refuses to remove is reported as a run warning and left for host cleanup.
Its state file is kept, so a later teardown of the same owner retries. Ownership
violations, and failed removals during `start`, still fail.

Regression tests prove `job=shard, instance=default` and
`job=shard-default, instance=<omitted>` coexist and either stop order preserves
the other. They also cover foreign labels, tampered state, field-boundary
collisions, retries and resource limits using a Docker CLI double.
collisions, retries, resource limits, `--init` and the teardown tolerance using
a Docker CLI double.

## Coverage, required check and partial reruns

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/features/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The task detail view exposes progress during execution, including streamed outpu

## Provider Capacity

With the optional [Agent Tank](../operations/agent-tank.md) integration enabled, provider capacity becomes a visible signal too: the sidebar shows live usage bars per subscription provider, and each LLM log entry records the usage delta its call consumed. Turn it on from the dashboard banner ProPR shows when it detects a running instance, from **Settings → LLM Usage Tracking**, or with `propr tank on`.
With the optional [Agent Tank](../operations/agent-tank.md) integration enabled, provider capacity becomes a visible signal too: the sidebar shows live usage bars per subscription provider, and each LLM log entry records the usage delta its call consumed. Turn it on from the dashboard banner, from **Settings → LLM Usage Tracking**, or with `propr tank bundled` — bundled mode runs Agent Tank inside the ProPR agent image, so there is nothing to install.

## Recovery

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/features/propr-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The full-screen wizard requires an interactive terminal. Over SSH or in shells w
- `propr init stack [--root <dir>]` creates `data/`, `logs/`, `repos/`, writes `.env` from the bundled template, and auto-detects agent credential directories on the host (`~/.claude`, `~/.codex`, `~/.gemini`, `~/.config/opencode`, `~/.vibe`).
- `propr check` reports the detected [GitHub auth mode](../operations/github-auth.md) (own App, relay, or demo) and flags missing or placeholder configuration before anything starts. `--verify` additionally runs an image/CLI smoke test per agent.
- `propr start --no-tui` starts without the interactive dashboard (for scripts/CI); `--no-pull` skips image pulls; `--restart` recreates running services.
- `propr tank [on|off] [--url <url>]` toggles [Agent Tank](../operations/agent-tank.md) LLM usage tracking on a running stack (omit the state to print the current setting).
- `propr tank [bundled|external|off] [--url <url>]` configures [Agent Tank](../operations/agent-tank.md) LLM usage tracking on a running stack (omit the mode to print the current one). `bundled` runs Agent Tank inside the agent image with nothing to install; `external` needs `--url` pointing at a daemon you run. `on` remains a deprecated alias for `external`.

### Agent Skill

Expand Down
Loading
Loading