134 Commits

Author SHA1 Message Date
EugeneTes
202cb001e7 Test oversize photo/document rejection 2026-09-08 12:20:48 +00:00
EugeneTes
f98572b39e Test explicit kind override for send 2026-09-08 12:18:06 +00:00
EugeneTes
40cb4a8061 Test document routing for non-image extensions 2026-09-08 12:15:46 +00:00
EugeneTes
a6612edc1d Type send.test.ts mocks against SendBotApi 2026-09-08 12:05:09 +00:00
EugeneTes
c30fccb6d5 Send photos through sendPhoto with auto-detection 2026-09-08 12:03:17 +00:00
EugeneTes
7c806d2d57 Reject missing files and directory paths in send 2026-09-08 11:59:43 +00:00
EugeneTes
3db5844c86 Reject send paths that escape the workspace 2026-09-08 11:55:25 +00:00
EugeneTes
3039f85db8 Add send tool skeleton with schema and factory 2026-09-08 11:51:33 +00:00
EugeneTes
ec87e8dcfd Add implementation plan for send-attachments tool 2026-09-08 11:49:13 +00:00
EugeneTes
0b2ae39651 Draft design for send-attachments tool
Adds a `send` custom tool so the agent can push files and images from the
workspace to the owner as Telegram attachments, closing the gap where the
bot could receive files but only ever reply with text.
2026-09-08 11:40:30 +00:00
EugeneTes
8b22645df4 Pipe MCP subprocess stderr to logger and fix bin lookup key
Two review followups:
- Consume the piped stderr from @playwright/mcp so browser-launch
  failures surface in our own logs (was set to "pipe" but never read).
- Drop the wrong ["mcp-server-playwright"] lookup; the actual bin key
  is "playwright-mcp". Falling through Object.values worked but was
  dead code.
2026-09-08 11:00:42 +00:00
EugeneTes
3a43391eb0 Document Playwright MCP env vars and manual E2E steps 2026-09-08 10:56:54 +00:00
EugeneTes
d69d01124d Install Chromium + deps in image for Playwright MCP 2026-09-08 10:55:34 +00:00
EugeneTes
8106c55e46 Wire Playwright MCP bridge into main.ts startup 2026-09-08 10:52:03 +00:00
EugeneTes
a3ce614171 Plan: fix fakeClient fixture to use live getters 2026-09-08 10:49:55 +00:00
EugeneTes
e190d1c714 Add Playwright MCP bridge module 2026-09-08 10:49:09 +00:00
EugeneTes
a4b50617cc Parse PLAYWRIGHT_* env vars into config.playwright 2026-09-08 10:44:39 +00:00
EugeneTes
82fdef8317 Add @modelcontextprotocol/sdk and @playwright/mcp deps 2026-09-08 10:41:03 +00:00
EugeneTes
9b0a90458c Add implementation plan for Playwright MCP bridge
Seven-task plan: deps → config parsing (TDD) → bridge module (TDD w/
fake MCP client) → main.ts wiring → Dockerfile Chromium install →
docs → verification.
2026-09-08 10:37:50 +00:00
EugeneTes
5b7d344207 Draft design for Playwright MCP bridge
Bridge @playwright/mcp as a stdio subprocess and re-export its tools
as Pi custom tools, gated on PLAYWRIGHT_ENABLED. Uses Type.Unsafe to
pass MCP inputSchema through to the model without hand-mapping.
2026-09-08 10:24:07 +00:00
EugeneTes
d889bd33f5 Ignore scope on plain single events per spec 2026-09-08 08:53:30 +00:00
EugeneTes
ae6485d3f4 Document recurring-event manual E2E check 2026-09-08 08:50:14 +00:00
EugeneTes
e0001ac9d2 Add list_instances action for recurring series 2026-09-08 08:46:02 +00:00
EugeneTes
a657c96664 Scope delete to instance or series 2026-09-08 08:41:09 +00:00
EugeneTes
7738b96558 Resolve update scope from event id and guard recurrence-on-instance 2026-09-08 08:35:29 +00:00
EugeneTes
4cb877df04 Add list_mode=series and recurring marker on list output 2026-09-08 08:30:32 +00:00
EugeneTes
737389bcf7 Support recurrence in create and RFC 5545 validation 2026-09-08 08:18:12 +00:00
EugeneTes
c5e2c51fd3 Extend calendar schema and types for recurring events 2026-09-08 08:06:33 +00:00
EugeneTes
160a615713 Add implementation plan for recurring calendar events 2026-09-08 08:00:59 +00:00
EugeneTes
30695cb121 Add design for recurring Google Calendar events
Extends the calendar tool to create, modify, and delete recurring events
via raw RFC 5545 recurrence lines, with instance vs. series scope on
update/delete and a list_instances action to fetch a series' occurrences.
2026-09-08 07:56:24 +00:00
EugeneTes
dbbecce36d Rename Telegram .oga voice files to .ogg for OpenAI transcription
Telegram delivers voice messages with a .oga extension (audio-only OGG),
which OpenAI's transcription API rejects even though the container bytes
are identical to .ogg.
2026-09-07 08:54:21 +00:00
EugeneTes
f876d1cfdf Set container timezone to Europe/Warsaw
Install tzdata so TZ resolves, default SECRETARY_TIMEZONE to
Europe/Warsaw, and pass it through as the container-wide TZ so OS
timestamps (logs, date, cron) match the app's scheduling zone.
2026-09-07 06:34:37 +00:00
EugeneTes
9fc348f0e2 Add Google Calendar as a native Pi custom tool
pi-claude-code-provider fences off Claude Code's MCP servers by design,
so managed MCPs like Google Calendar can't reach the bot through that
provider. Implement calendar access as a first-class Pi tool instead —
it lives alongside `schedule` and calls Google Calendar API v3 directly
with a subscription-free refresh-token grant.

Config: three new optional env vars (GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN) — set all three to enable
the tool, leave all three blank to disable. Partial config raises a
ConfigError at startup so mis-set env is caught early.

Tool: list / create / update / delete on any calendar (default
'primary'). Access tokens are cached until expiry and refreshed on
demand from the long-lived refresh token. Invalid_grant surfaces a
clear "re-run device flow" error.
2026-09-06 20:51:00 +00:00
EugeneTes
dacebf9329 Revert MCP-passthrough patch to pi-claude-code-provider
The provider validates on every session init that Claude launched with
exactly its 10 mcp__pi__* proposal tools. Removing --tools "" and
--setting-sources "" made Claude Code load its built-ins plus every
extension/skill/managed-MCP tool on the user's account, which trips
the "unexpected tool set" isolation check and refuses the session.

Bump the install marker to .installed.v2 and rm -rf the package dir
before install so bind-mounted volumes carrying the previously-patched
copy get a clean reinstall. Google Calendar access via MCP through
this provider is not viable — the provider is adversarially designed
against exactly this. Implement calendar as a native Pi custom tool
if we want it in the bot.
2026-09-06 20:22:18 +00:00
EugeneTes
92e4df5f84 Let Claude Code MCP servers reach the pi provider
pi-claude-code-provider deliberately isolates its Claude subprocess by
hard-coding --strict-mcp-config, --setting-sources "" and --tools "",
which hides every user-configured MCP server (Google Calendar, etc.)
from the model. Patch those three flag-pairs out of the installed
provider's src/claude-args.ts on every start (idempotent — no-op once
they're gone). Trade-off: Claude can now invoke MCP/web/file tools
invisibly, forfeiting the provider's execution-authority guarantee.
Acceptable for a personal Telegram bot.
2026-09-06 20:17:57 +00:00
EugeneTes
5ae4e5092c Stop filtering MCP tools out of Pi sessions
pi-backend passed a hard-coded `tools: ENABLED_TOOLS` allowlist into
createAgentSessionFromServices. Pi treats that as `allowedToolNames`
and filters every tool — built-in, extension, MCP — whose name isn't
in the list. Any MCP server the user enabled (Google Calendar, ...)
was silently invisible to the model.

Drop the allowlist. Instead, after bindExtensions returns (once MCP
servers have registered their tools), activate every tool in the
registry via setActiveToolsByName. That covers built-ins the SDK
doesn't turn on by default (grep, find, ls), custom tools like
`schedule`, and every MCP tool the user has connected.
2026-09-06 19:18:37 +00:00
EugeneTes
617f3e5c16 Drop sleep-infinity override, boot the bot normally 2026-09-06 18:05:03 +00:00
EugeneTes
f1933d796a Bind-mount state to /media/disk1/containers/volcanic_secretary
Switch from named volumes to bind mounts under one host directory so
the workspace, pi-agent, and claude state is easy to back up / inspect
on the Coolify host.

Also re-adds the sleep-infinity override so the container stays up
while claude auth is redone against the new (empty) claude bind mount.
2026-09-06 17:59:29 +00:00
EugeneTes
0a49bd28e0 Persist Claude Code auth across redeploys
Mount a named volume at /root/.claude so `claude auth login` state
(read by pi-claude-code-provider's preflight) survives container
recreation.
2026-09-06 17:55:50 +00:00
EugeneTes
264ab7a945 Drop the sleep-infinity override, boot the app normally
Pi is configured now; the container should run node dist/main.js.
2026-09-06 17:54:38 +00:00
EugeneTes
fe70fa10e1 Put pi on PATH and install Claude Code CLI in the image
Two additions:
- ENV PATH prepends /app/node_modules/.bin so `pi` is on PATH inside
  the container (previously `docker exec ... pi ...` failed with
  "command not found").
- `npm install -g @anthropic-ai/claude-code` so the `claude` CLI is
  available — pi-claude-code-provider shells out to it.
2026-09-06 17:43:30 +00:00
EugeneTes
4296157689 Make docker-entrypoint pass through command args
The node:24 base image sets ENTRYPOINT ["docker-entrypoint.sh"]. We
override that script but the ENTRYPOINT still runs on every start, so
compose's `command:` was being handed to our script as args (and
silently ignored — we always exec'd `node dist/main.js`). That made the
`sleep infinity` override useless: the crash-looping app kept running.

Fix by execing "$@" when args are present so compose overrides work,
and set CMD to ["node","dist/main.js"] so normal (no-override) starts
still boot the app.
2026-09-06 17:38:54 +00:00
EugeneTes
1b2d2dc4d0 Idle the container so pi.dev auth can be configured manually
Override the compose command with `sleep infinity` for now — the deploy
failed because Pi has no auth for pi.dev yet. Once the user has run
`pi login` (or dropped an auth.json in /pi-agent) inside the container,
remove the `command:` line to restore normal app startup.
2026-09-06 17:29:46 +00:00
EugeneTes
8c6b4c1a54 Install pi-claude-code-provider on first container start
The Pi coding agent stores installed packages under $PI_CODING_AGENT_DIR
(=/pi-agent in the image), which is a volume — so anything installed at
build time is masked at runtime. Wrap the CMD in an entrypoint that runs
`pi install npm:pi-claude-code-provider` the first time the volume is
seen and drops a marker so subsequent starts are a no-op.
2026-09-06 17:21:03 +00:00
EugeneTes
0eed2fa36d List usable model ids when PI_MODEL points at a provider without credentials
Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:38:43 +02:00
EugeneTes
59b11eda14 Add Windows launcher script and Telegram setup guide
Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:30:13 +02:00
EugeneTes
bf35eee736 Log crash stacks, warmer /start, correct command detection, and stricter fallbacks (Group D)
- main.ts: unhandledRejection/uncaughtException also print the stack (or
  message) via console.error so it is not lost when the logger only prints a
  single line; bot.start(...) is no longer fire-and-forget -- a polling
  failure is logged and exits the process instead of failing silently.
- bridge.ts: /start now greets before showing the same status block as
  /status (split back into two command handlers). Command detection now
  checks for a bot_command entity at offset 0 instead of a leading "/", so a
  message that merely starts with a slash (e.g. a path) reaches the agent
  instead of getting "Unknown command.". An unsupported media message with
  no text and no other notes (sticker, location, video note, ...) now gets
  an explicit "I can handle text, photos, voice notes, and files." reply
  instead of silently doing nothing.
- outbound.ts sendMarkdown: only falls back to plain text for Telegram's
  400 "can't parse entities" style errors; any other error (network, etc.)
  propagates instead of being masked as a silent plain-text resend.
- runner.ts resolveShell: added the per-user Git for Windows bash.exe under
  %LOCALAPPDATA% as a third candidate; README's Windows section now
  mentions SECRETARY_SHELL for that case.
- docker-compose.yml: added init: true, since Node does not reap zombies
  left by killed script process groups.

Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:09:21 +02:00
EugeneTes
9cf6f7385e Cap captured script output, scrub the bot token, and speed up scheduler tests (Group C)
- runner.ts: cap captured stdout/stderr at 64 KiB per stream via a new
  Capture helper, keeping the first 48 KiB and last 16 KiB once a stream
  exceeds the cap, joined by "\n...(truncated)...\n". Prevents a runaway
  watcher script from ballooning memory or a Telegram message.
- runner.ts runScript: delete TELEGRAM_BOT_TOKEN from the child environment
  before spawning, since model-written scripts must never be able to read
  and exfiltrate it. Spec section 6.2 updated to describe the exclusion.
- scheduler.test.ts: the "drain() gives up after its timeout" test no longer
  calls s.stop() at the end -- no timer is armed after the at job fires, and
  its delivery never resolves, so stop()'s 10s grace period would run out
  for nothing. Full suite now runs in ~1.5s.
- runner.test.ts: relax the timeout-kill elapsed-time assertion from 3000ms
  to 4000ms to reduce flakiness, per the review.

Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:07:12 +02:00
EugeneTes
efacd2b791 Check provider auth at startup and add current time to the prompt (Group B)
- pi-backend.ts start(): after confirming a model resolved, also confirm the
  provider has usable credentials via ModelRuntime.hasConfiguredAuth /
  checkAuth, so a missing-auth failure surfaces as a clear startup error
  instead of the first prompt failing later. checkAuth resolves to an
  AuthCheck object or undefined (not a boolean), so the existing
  `!(await mr.checkAuth(provider))` truthiness check already does the right
  thing with no further adaptation.
- The inline extension's before_agent_start handler now also appends the
  current date/time in the configured timezone, so the model knows "now"
  without a tool call. Extracted as system-prompt.ts's currentTimeLine(),
  unit tested with a fixed date.

Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:06:56 +02:00
EugeneTes
d2a0ec2cf1 Join every turn-final answer instead of only the last (Group A)
Pi drains queued follow-ups and steers inside the same run with no
agent_end/agent_start between them, so when a scheduled job's follow-up (or
a user steer) merges into a run, the model can answer twice before
agent_settled fires once. AgentHost.settle() used only the last assistant
message as finalText, so the first answer was lost (it only ever appeared
as progress-message narration, which gets deleted).

RunRecord now tracks `finals`: assistant texts whose stopReason is not
toolUse (and, as before, not error/aborted), collected alongside `texts`.
settle() joins `finals` with blank lines, falling back to `texts` and then
to the placeholder, matching the documented fallback chain.

Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
2026-09-06 18:06:46 +02:00