Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
7b4bcf0
[failproofai-feat-failproofai-cloud-login] Sign in to `fp` from a bro…
SiddarthAA Nov 4, 2026
825ff35
[failproofai-feat-failproofai-cloud-login] Add the PR number to the c…
SiddarthAA Nov 4, 2026
47c522d
[failproofai-feat-failproofai-cloud-login] Harden `fp login` against …
SiddarthAA Nov 4, 2026
4a61405
[failproofai-feat-failproofai-cloud-login] Honour Esc pressed during …
SiddarthAA Nov 4, 2026
aebcd46
[failproofai-feat-failproofai-cloud-login] Test the address rules on …
SiddarthAA Nov 4, 2026
131d094
[failproofai-feat-failproofai-cloud-login] Read every pending key bef…
SiddarthAA Nov 4, 2026
7c549b7
[failproofai-feat-failproofai-cloud-login] Refuse a claimed token no …
SiddarthAA Nov 4, 2026
cfa9e57
[failproofai-feat-failproofai-cloud-login] Don't let a key pressed du…
SiddarthAA Nov 4, 2026
f7b7001
[failproofai-feat-failproofai-cloud-login] Keep the old prompts when …
SiddarthAA Nov 4, 2026
fe8aeb4
[failproofai-feat-failproofai-cloud-login] Fix a race in the E2E, che…
SiddarthAA Nov 4, 2026
fbb0fd3
[failproofai-feat-failproofai-cloud-login] Ask for the email first, w…
SiddarthAA Nov 5, 2026
c64f84a
[failproofai-feat-failproofai-cloud-login] Take fp back to its earlie…
SiddarthAA Nov 5, 2026
23e5863
[failproofai-feat-failproofai-cloud-login] Say enter and SSO in the l…
SiddarthAA Nov 5, 2026
42a6b4e
[failproofai-feat-failproofai-cloud-login] Start fp login on the brow…
SiddarthAA Nov 5, 2026
2c17534
[failproofai-feat-failproofai-cloud-login] Fall back to an emailed co…
SiddarthAA Nov 7, 2026
c89ef74
Merge remote-tracking branch 'origin/main' into feat/failproofai-clou…
SiddarthAA Nov 7, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 1.0.14-beta.0 — 2026-11-02

### Features

- `fp login` can sign you in from a browser, as `failproofai config` does: on a terminal it starts on a link, Tab switches to a code by email, and either way you get your own session. New deliberate-run harness: `__tests__/e2e/layout/fp-login.sh`. (#981)

### Docs

- Add public pages for the MCP server, the Slack app and product analytics, which the assistant and MCP server can now answer from (#982).
Expand Down
8 changes: 8 additions & 0 deletions __tests__/e2e/layout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ not depend on — so they are invoked deliberately.
| `cloud-collector.sh` | an AgentEye stack | real connect against a live server, daemon + collector writing layout-2 paths, decisions reaching ClickHouse, disconnect back to OSS |
| `systemd-service.sh` | Docker | REAL `systemctl enable --now` as root in a privileged container: service lifecycle, fail-closed, self-heal, reinstall |
| `cli-login.sh` | an AgentEye stack (dashboard + mailpit + its Postgres), an older Cloud server without `/api/auth/cli/*` routes, Docker, `bun run build` + the release daemon | `config`'s browser login through a real pty in a systemd container: approve, re-login, deny + retry, abandon after approval (no key minted), esc, expiry, revoked saved key, older server, and the person-side refusals (Origin, form posts, no session, start rate limit) |
| `fp-login.sh` | an AgentEye stack (dashboard + mailpit + its containers), `uv sync` in `fp-cloud-cli`; optionally a Cloud with `config`'s login but not `fp`'s, and a dashboard with `DASHBOARD_TRUSTED_INGRESS=1` | `fp login`'s browser flow through a real pty: approve, the org picker, the email code with tab, tab to the email and back, deny + retry, expiry, esc and ctrl-c, an approval nobody collects (no session), a dropped connection, opening the browser, Clouds without it (nothing minted), the network warning and when the email goes out, the person-side refusals, and no secret in any log |

`systemd-service.sh` exists because the service half cannot be tested on a
developer machine without a password prompt, and a container gives real systemd
Expand Down Expand Up @@ -38,6 +39,13 @@ bash __tests__/e2e/layout/systemd-service.sh
DASH=http://localhost:3000 MAILPIT=http://localhost:8026 OLD_CLOUD=http://localhost:28080 PG_CONTAINER=agenteye-postgres-1 \
bash __tests__/e2e/layout/cli-login.sh

# fp-login.sh drives the real `fp` the same way. STACK is the compose project, which
# names the containers it reads. Add CLOUD_1090 and PG_1090 to cover a Cloud from
# before `fp`'s half of this, and TRUSTED_DASH for the different-network warning. It
# stops and restarts the dashboard container once.
DASH=http://127.0.0.1:3000 MAILPIT=http://127.0.0.1:8026 STACK=agenteye \
bash __tests__/e2e/layout/fp-login.sh

# cloud-collector.sh expects the stack on the remapped ports (18080/18123) so it
# can run alongside an existing project rather than displacing it:
docker compose -p aefpai -f docker-compose.yml -f ports.yml up -d
Expand Down
28 changes: 22 additions & 6 deletions __tests__/e2e/layout/cli-login-driver.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#!/usr/bin/env python3
"""Drive `failproofai config` through a real pseudo-terminal, screen by screen.
"""Drive a login through a real pseudo-terminal, screen by screen.

Used by cli-login.sh, inside the systemd container, as the unprivileged user.
The wizard only offers browser login to a real terminal, so a pty is the only
honest way to test it end to end.
Used by cli-login.sh for `failproofai config` (inside the systemd container, as the
unprivileged user) and by fp-login.sh for `fp login`. Both only offer browser login
to a real terminal, so a pty is the only honest way to test it end to end.

Usage: cli-login-driver.py <script> <transcript-file> <url-file> -- <command...>

<script> is a comma-separated list of steps, each `wait:<text>` (block until
the screen shows <text>), `key:<name>` (enter, down, up, esc, ctrl-c, tab),
`url` (wait for the authorize link and write it to <url-file>), or
`sleep:<seconds>`. Exit status is the command's own, or 124 on a timeout.
`url` (wait for the authorize link and write it to <url-file>), `sleep:<seconds>`,
`type:<text>`, or `typefile:<path>` (wait for that file, then type what it holds:
for a value the script only learns later, like an emailed code). Exit status is
the command's own, or 124 on a timeout.
"""
import os
import pty
Expand Down Expand Up @@ -83,6 +85,20 @@ def plain() -> str:
elif kind == "key":
time.sleep(0.3) # let the screen settle so the key lands on it
os.write(fd, KEYS[arg])
elif kind == "type":
time.sleep(0.3)
os.write(fd, arg.encode())
elif kind == "typefile":
# Not just "exists": the shell creates the file before it writes to it.
data = b""
while not data:
if os.path.exists(arg):
with open(arg, "rb") as f:
data = f.read().strip()
if not data and (time.time() > deadline or not pump(0.2)):
print(f"driver: {arg} never appeared", file=sys.stderr)
return 124
os.write(fd, data)
elif kind == "sleep":
end = time.time() + float(arg)
while time.time() < end:
Expand Down
6 changes: 4 additions & 2 deletions __tests__/e2e/layout/cli-login.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ OUT=$(mktemp -d)
PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); printf ' \033[32mPASS\033[0m %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf ' \033[31mFAIL\033[0m %s\n %s\n' "$1" "${2:-}"; }
has() { if printf '%s' "$2" | grep -qF -- "$3"; then ok "$1"; else bad "$1" "missing [$3] in: $(printf '%s' "$2" | tr '\n' ' ' | tail -c 400)"; fi; }
hasnt() { if printf '%s' "$2" | grep -qF -- "$3"; then bad "$1" "unexpected [$3]"; else ok "$1"; fi; }
# Not `printf | grep -q`: on input bigger than a pipe buffer grep exits at the first match,
# printf dies of SIGPIPE, and under pipefail a found string reads as a missing one.
has() { if [[ "$2" == *"$3"* ]]; then ok "$1"; else bad "$1" "missing [$3] in: $(printf '%s' "$2" | tr '\n' ' ' | tail -c 400)"; fi; }
hasnt() { if [[ "$2" == *"$3"* ]]; then bad "$1" "unexpected [$3]"; else ok "$1"; fi; }
eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "want [$3] got [$2]"; fi; }
head1() { printf '\n\033[1m── %s\033[0m\n' "$1"; }
jqp() { python3 -c "import sys,json;d=json.load(sys.stdin);print(eval('d'+sys.argv[1]))" "$1" 2>/dev/null; }
Expand Down
Loading
Loading