100 Commits
Author SHA1 Message Date
EislM0203 02b2f038eb fix release 2026-06-29 21:50:45 +02:00
EislM0203andmarkus.eisl 9d05a67512 custom-providers: live loaded-model status + thinking support for llamacpp
- Extend ProviderStatus with loadedModels[] populated from /running;
  llamacpp panel now shows active model name instead of just "N models"
- Return DiscoverResult from discoverProvider (models + loadedModels)
- All llamacpp models get reasoning:true + compat.thinkingFormat:"qwen-chat-template"
  so Ctrl+T / Shift+Tab work for both Qwen3 and Gemma4 (verified live)
2026-06-29 16:28:30 +02:00
EislM0203 336d26bfcd fix: correctness and reliability bugs across sync and k8s extensions
- s3: guard resp.Body against undefined before calling transformToByteArray
- s3: throw instead of returning 0 when both pi-mtime metadata and
  LastModified are absent (non-conformant S3 response)
- s3: parallelize HeadObjectCommand calls per list() page with Promise.all
  instead of serial awaits, reducing N+1 round-trips to one per page
- sync: emit skip(local-only-in-pull) for local-only files in pull mode so
  they appear in the report instead of being silently dropped
- manifest: convert readManifest/writeManifest to async fs.promises to
  avoid blocking the Node.js event loop on slow/network filesystems
- panel: track lastError separately from lastReport so a sync exception
  shows a persistent error line instead of leaving the previous report
- k8s/diag: set identity to "(auth whoami unavailable)" when the can-i
  --list fallback succeeds, instead of leaving it as "(unknown identity)"
- k8s/index: warn when a configured helper model is not found in the
  registry rather than silently falling back to the active model
2026-06-29 13:41:03 +00:00
EislM0203andClaude Opus 4.7 0ef000e623 sync ext: drop auto pull/push, make /sync pull always override local
- Remove session_start (pull) and session_shutdown (prune+push) handlers
  from extensions/sync/index.ts; sync is now driven only by the /sync
  panel and subcommands.
- /sync pull now downloads every file present in S3 regardless of local
  mtime or manifest state. The first-pull manifest-absence gate is gone;
  the download reason is renamed first-pull -> force-pull.
- Update sync.test.ts: the former LWW test now asserts pull overrides
  local even when the manifest records the key.
- README: drop the "Lifecycle hooks" section and rewrite the conflict
  model to reflect "remote always wins for pull".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 00:31:08 +02:00
EislM0203 9fb63148fe k8s ext: helper-model summarization, missing-kubectl + TLS env handling
- Summarize k8s_get/describe/logs/events through a helper model when
  output exceeds a configurable threshold (default 4000 chars). Optional
  `prompt` biases the summary; `raw=true` bypasses it. Config + helper
  picker live in the /k8s panel. Naming mirrors websearch
  (helperModel/helperTimeoutMs/helperMaxTokens/helperUsed).
- PI_K8S_INSECURE_SKIP_TLS now accepts 1/true/yes/on so SOPS can encrypt
  the value as a string.
- classifyKubectlResult recognises ENOENT (empty stdout+stderr, code≠0)
  as kind=missing-binary; diag short-circuits with a clear panel state
  and session_start warns once.
- Sync: pull on a fresh machine no longer skips files where the local
  default has mtime>remote — first-pull (no manifest entry) forces a
  download. Fixes custom-providers.json reverting on every sandbox.
- websearch typecheck fix: replace DOM Document reference with `as never`.
2026-06-01 15:25:51 +02:00
EislM0203andClaude Opus 4.7 4f387a278b add S3-backed config + memory sync extension
New extensions/sync/ that bidirectionally syncs ~/.pi/agent/ configs +
memories with an S3 bucket so fresh OpenShell sandboxes (or new laptops)
boot with existing pi state.

- Allowlist: settings.json, k8s.json, websearch.json, custom-providers.json,
  hosts.json, local-models.json, memory/**. Excludes auth.json
  (provider API keys; injected per-sandbox).
- Per-file last-write-wins by real mtime carried in x-amz-meta-pi-mtime;
  1s clock-jitter tolerance.
- Per-machine ~/.pi/agent/.sync-manifest.json records what was last seen
  on S3 so auto-prune can't wipe a concurrent laptop's data.
- Manual /sync command opens a TUI panel; subcommands /sync pull, push,
  prune, status work without the panel.
- session_start auto-pulls (5s soft timeout, non-blocking).
- session_shutdown auto-prunes then auto-pushes (10s hard timeout).
- Config via PI_AWS_* env vars only (chicken/egg: nothing to bootstrap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 22:01:58 +02:00
EislM0203andClaude Opus 4.7 9c64807ac3 plan: S3-backed config + memory sync extension
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 20:43:13 +02:00
EislM0203andClaude Opus 4.7 4ad5b643f8 spec: S3-backed config + memory sync extension
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 20:36:19 +02:00
EislM0203andClaude Opus 4.7 2fa12cd8ea k8s ext: allow setting the SA token via the /k8s panel
The env-only constraint on KUBE_TOKEN only mattered for OpenShell's
placeholder rewriting; since the extension runs outside OpenShell, the
token is just a plain credential the user supplies. Mirror the cluster
URL flow: press `t` in /k8s to set the token, persisted to
~/.pi/agent/k8s.json (0600).

- config.ts: K8sConfig gains optional `token`
- resolveK8sEnv: env first, then config file, for both URL and token
- panel: `t` affordance + token-source status line; prompts never
  prefill/echo the secret (ctx.ui.input has no masking)
- fix: promptApiServer now merges instead of overwriting (was dropping
  a saved token); both prompts read-merge-write
- README documents the plaintext-on-disk tradeoff

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:49:38 +02:00
EislM0203andClaude Opus 4.7 415150c6a3 add k8s read-only troubleshooting extension
A new extensions/k8s/ extension giving the agent read-only Kubernetes
tools (k8s_get, k8s_describe, k8s_logs, k8s_events, k8s_top, k8s_explain,
k8s_context). Credentials: KUBE_TOKEN from the env, cluster URL from
KUBE_APISERVER env or ~/.pi/agent/k8s.json (editable via the /k8s panel).

All kubectl invocations go through one exec.ts chokepoint that prepends
--server/--token; the token never reaches any render path. A /k8s panel
runs reachability + can-i probes and lets the user set the cluster URL
(press e). A bundled "k8s" mode restricts the toolset to read + grep +
k8s_* for focused troubleshooting. README ships the SA + RBAC manifest.

Container changes (Dockerfile):
  - install kubectl
  - bake customizations into /app (OpenShell Landlock allowlists /app,
    not /opt) and chmod world-readable so the remapped sandbox uid can
    read them
  - openshell-policy.yaml: baseline filesystem/landlock/process policy

Also deletes the unused status-line.ts demo extension.

Known limitation: does NOT work inside an OpenShell sandbox yet —
OpenShell hardcodes the k8s control-plane ports (6443 et al.) as
universally blocked in its SSRF engine with no override. Documented in
extensions/k8s/README.md ("Why not OpenShell yet"). The extension works
when pi runs directly on a host with cluster access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:34:44 +02:00
EislM0203andClaude Opus 4.7 7c650dd13e plan: k8s read-only extension implementation
10-task plan for extensions/k8s/: exec chokepoint + 7 typed tools +
diag/panel + index wiring + bundled k8s mode + README. TDD per task
with vitest unit tests on all pure helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:33:52 +02:00
EislM0203andClaude Opus 4.7 e54830099f spec: drop streaming output from k8s ext (pi.exec is one-shot)
pi.exec returns one captured result; ssh-hosts uses its own streamer.
For the k8s extension, single-shot output with tail caps is fine and
simpler. Streaming can be added later if log latency proves to matter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:27:10 +02:00
EislM0203andClaude Opus 4.7 513cafed8a docs: spec for k8s read-only extension
Brainstormed design for an extensions/k8s/ extension that gives the
agent read-only kubectl tools, credentialed via an NVIDIA OpenShell
provider injecting KUBE_APISERVER + KUBE_TOKEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:34:52 +02:00
EislM0203andClaude Opus 4.7 854b4c2319 add todos extension: per-session ledger as a drift anchor
A single todo_write tool (add/complete/update/remove/clear) writes
full-state snapshots to the session via appendEntry. The current list
is injected into the system prompt each turn and surfaced as a
done/total counter in the status line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:58:39 +02:00
EislM0203 b8af9890e8 fix Dockerfile 2026-05-20 08:01:40 +02:00
EislM0203andClaude Opus 4.7 13b51ed7ea custom-providers panel: edit truly preserves existing values
The pi ExtensionUI.input(title, placeholder?, opts?) second arg is a
placeholder hint, not an initial value — there is no API for an
initial value. So on edit, hitting Enter on an "empty" field was
actually submitting an empty string. The previous panel pattern
silently turned that into the empty string, making edit behave
identically to add. Fix:

- Show current value in the title: "X (current: Y, blank to keep)".
- Treat empty trimmed input as "keep current" for every field.
- Esc (input returns undefined) still cancels the whole flow.
- Removed the now-unused promptField helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:24:48 +02:00
EislM0203andClaude Opus 4.7 d22a7807d2 custom-providers panel: kind-aware URL prompt + current-kind pre-select
Two UX fixes after live testing:

- The select dialog has no native pre-fill, so on edit the current
  kind was invisible. Reorder the options so the existing kind is
  first (Enter accepts it) and append "(current: <kind>)" to the
  prompt label.
- Asking ollama users for an "OpenAI-compatible base URL" is the
  wrong mental model. Once kind is chosen, prompt for the bare
  server URL ("Ollama server URL (e.g. http://localhost:11434)" or
  "llama-swap server URL (e.g. http://localhost:9000)") and append
  /v1 on save. Edit prefill strips /v1 + trailing slashes so users
  see the bare host. Storage shape (baseUrl with /v1) is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:17:18 +02:00
EislM0203andClaude Opus 4.7 7da370744e custom-providers panel: kind select + [kind] column
Replace the transitional kind placeholder with a proper ctx.ui.select
step in the add/edit flow. List row gains a [kind] column between
name and baseUrl. Required fields (name, kind, baseUrl) all cancel
with a consistent warning notify; apiKey and defaultCtx remain
optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:02:15 +02:00
EislM0203andClaude Opus 4.7 6882d80ba2 custom-providers: derive nativeUrl from already-trimmed baseUrl
Cosmetic consistency fix surfaced in code review — both fetch URLs
now compose from the trimmed `baseUrl` local, making the trim/derive
pipeline read as a single chain. Behavior unchanged; the previous
regex /\/v1\/?\$/ matched both with and without trailing slash, but
the asymmetry was easy to misread.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:00:58 +02:00
EislM0203andClaude Opus 4.7 8932846449 custom-providers: kind discriminator, drop nativeUrl
Add required kind: "ollama" | "llamacpp" to CustomProvider; remove
nativeUrl. Discovery now dispatches on kind: ollama probes /api/show
per model, llamacpp probes /running once. nativeUrl is derived inline
from baseUrl (strip trailing /v1). loadConfig validates kind and
all other field types; a missing or unrecognized kind makes the file
"corrupt" and the seed file is used in memory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:58:00 +02:00
EislM0203andClaude Opus 4.7 ec8ed9cc2a Rename local-models extension to custom-providers
Mechanical rename: directory, type (LocalProvider → CustomProvider),
slash command (/local-models → /custom-providers), config path
(local-models.json → custom-providers.json), panel title, and stale
comment references in subagent extensions. No behavioral change —
kind discriminator and nativeUrl removal land in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:50:54 +02:00
EislM0203andClaude Opus 4.7 b1be8bd224 Add implementation plan for custom-providers rename + kind unification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:40:28 +02:00
EislM0203andClaude Opus 4.7 3ef3300fee extensions: replace the built-in startup header with the π mascot
The default header was a version line + keybinding crib + "press ctrl+o
for more" reminder, repeated every session start. Swap it for the block-
art π mascot example that ships in @earendil-works/pi-coding-agent's
examples/extensions/. Subtitle trimmed from the example's self-
deprecating tagline to a plain "pi vX.Y.Z".

Hits ctx.ui.setHeader() on session_start. Keybinding hints still
reachable via ctrl+o, and /builtin-header restores the old display if
needed.

The [Prompts] / [Extensions] block printed after the header is a
separate "loaded resources" view controlled by the quietStartup setting
in ~/.pi/agent/settings.json — toggle that to silence it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:29:31 +02:00
EislM0203andClaude Opus 4.7 f5cf1c4598 websearch: collapse advanced config knobs behind an 'a' toggle
The /websearch panel had 11 rows after v1.1 wired up the new tunables —
too dense for the common case. Default view now shows only the 5
load-bearing fields (URL, max results, char limit, sandbox, helper);
press 'a' to reveal the advanced section (fetch/helper timeouts,
helper max tokens, user-agent, size cap multiplier, max redirects).

Advanced shortcuts (f/l/k/u/z/r) only trigger when the section is
visible — pressing them in basic view is a no-op rather than a silent
config edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:26:06 +02:00
EislM0203andClaude Opus 4.7 6fb4be1ad0 extensions: remove auto-commit-on-exit
It auto-commits every session with "[pi] <first line of last assistant
message>" — those commits land in the middle of intentional work and
need to be soft-reset + reauthored constantly. Easier to remove than
to retrain around.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:25:03 +02:00
EislM0203andClaude Opus 4.7 08fd0ccc90 websearch: trim fetch_url renderResult — show length, not a body snippet
The previous renderResult sliced the first 200 chars of the markdown
output and put it on the result line. For a helper-summary fetch that
meant the first sentence of an LLM's answer; for raw fetches it was
opaque markdown ("# Configuration\n\nPaperless provides..."). Either
way the snippet repeated content the user can already see in the full
result panel and added no signal.

Replace with a length-on-success / kind-on-failure summary using the
structured details we already bubble up:
  ✓ 14300 chars
  ✓ 80012 chars (truncated)
  ✗ http 403
  ✗ cross-domain-redirect
  ✗ too-large

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:23:59 +02:00
EislM0203andClaude Opus 4.7 ad07c25711 websearch: drop dead cross-host redirect bookkeeping
A2 (commit 4978123) made it impossible for a successful fetch to end
on a different host than the request: followRedirects refuses any
cross-host hop, so the success path always returns a finalUrl on the
original host. The crossHostRedirect bookkeeping kept around after
that change was always false:

- classifyRedirect() / its prefix string / FetchSuccess.crossHostRedirect
  / successDetails.crossHostRedirect — all dead.

Removed across fetch.ts, index.ts, and the tests. ~25 lines net. If
we ever want to allow some cross-host redirects (trusted-host
allowlist), the field can come back then.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:23:23 +02:00
EislM0203andClaude Opus 4.7 03a8b23833 websearch: v1.1 surface — opts-object fetchUrl, structured search outcome, panel knobs
Three public-surface cleanups now that the cache is gone.

fetchUrl(url, opts): charLimit is now part of FetchOpts instead of a
positional sibling. Everything tunable lives in one place. All call
sites updated.

SearchOutcome now carries `kind` and optional `status` on failure,
matching FetchOutcome's shape. SearchErrorKind = "not-configured" |
"network" | "http" | "body". The web_search tool surfaces both in
result.details so the calling agent can branch without parsing
strings (same pattern A5 introduced for fetch).

panel.ts exposes every config knob the v1.0 set added: fetch /
helper timeouts, helper max tokens, user agent, size cap multiplier,
max redirects. New single-letter shortcuts (f/l/k/u/z/r). The
prompt-for-positive-int pattern is collapsed into one helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:18:58 +02:00
EislM0203andClaude Opus 4.7 e3af1f275a websearch: remove the fetch-result cache
The in-memory page cache was solving the wrong problem for pi's usage.

What it cost:
- ~120 lines across fetch.ts, config.ts, and the test files
- The "negative caching" footgun: a transient 503 or DNS blip poisoned
  the cache for 15 minutes with no retry path
- Recurring maintenance to keep the cache key in sync with new opts
  (we just did this round in df2b1cd)

What it gave back, in practice:
- A speed win on the rare case where an agent fetches the exact same
  URL twice in a 15-min window. Typical agent loops fetch different
  URLs per iteration, so this hit rate is near zero for interactive use.

Removed: CacheEntry / cache Map / cacheKey / cacheGet / cacheSet /
__clearCache, the cacheTtlMs config field, the clearFetchCache call in
session_start, and 6 cache-specific tests. fetchUrl is now a thin
wrapper around the renamed performFetch.

103 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:16:10 +02:00
EislM0203andClaude Opus 4.7 df2b1cdafa websearch: cache correctness — full opts in fetch key, loadConfig memoized
Two cache holes that mattered.

fetch.ts cache key was (url, charLimit) only. Other options that affect
the cached outcome — sizeCapMultiplier, maxRedirects, userAgent —
weren't keyed. Fetching the same URL with a smaller sizeCapMultiplier
after a successful larger-cap fetch returned the prior outcome and
silently skipped the new size check. Now the key includes every option
that changes the result content (timeoutMs and cacheTtlMs don't — same
outcome, just different timing).

loadConfig() was reading websearch.json from disk on every tool call —
two reads per fetch_url, one per web_search. Memoize per filePath, with
invalidation on save (saveConfig updates the cache) and on session_start
(picks up external edits). Tests get __invalidateConfigCache in
beforeEach/afterEach so they don't leak state across cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:10:11 +02:00
EislM0203andClaude Opus 4.7 4710a6176f websearch: pure-helper refactor pass — no behavior change
Five internal cleanups that pay off in readability and remove a latent
ordering bug, none of which change observable output:

- dom-shim.d.ts: small local DOM type interface for linkedom (which
  ships near-empty types). Replaces ~11 `any` / `as any` sites across
  html-extract and site-extractors with typed parameters and accesses.

- html-extract: snapshot the body's outerHTML before Readability runs,
  so the fallback path reparses a clean tree instead of mutating the
  document Readability already operated on. stripChrome is now only
  ever called on throwaway docs. Pure helpers bodyToMarkdown() and
  runReadability() replace inline mutation.

- fetch: combine buildRedirectPrefix() + isCrossHost() into one
  classifyRedirect() that parses the URL pair once and returns both
  fields. Removes a duplicate try/catch.

- fetch: split fetchUrlNoCache from ~90 lines into named helpers
  (isHtml, isTextual, truncateAtCharLimit, checkDeclaredSize,
  readBodyAsMarkdown). Same control flow, the top-level function is
  now scannable.

- index: rename wrapWarning → helperUnavailableResult with a real
  SuccessDetails type for its parameter; the prior signature was an
  any-typed assembly that grew accidentally during A5.

All 105 tests pass; live URLs (github PR, wikipedia, reddit) extract
the same byte counts as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:04:12 +02:00
EislM0203andClaude Opus 4.7 1b3d2b0a7c websearch: fix size-cap regression, clamp web_search count to maxResults
Two real bugs surfaced in actual use.

Size cap rejected normal pages. The streaming size guard was checking
total bytes against charLimit × multiplier, which for a user with the
old fetchCharLimit=10000 (the default before the v1 bump) gave a 100 KB
ceiling. Almost every modern web page is 200–700 KB of HTML, so every
fetch failed as too-large after we had already extracted the content.
The original intent — don't download multi-MB binary dumps — is now
served by:

- Streaming reader truncates instead of rejecting. We keep what we've
  read up to the cap, mark truncated=true, and continue to extraction.
- Content-Length pre-check still refuses obvious oversize downloads.
- An absolute 2 MB floor on the byte cap, independent of charLimit, so
  small charLimit values don't cause spurious rejections.

web_search count not clamped. The model could pass count=10 even when
the user had configured maxResults=8. Now Math.min(requested,
config.maxResults) — the config is the ceiling.

Live-tested all six URLs from the user's failing transcript at the
broken charLimit=10000 — all return content now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:41:16 +02:00
EislM0203andClaude Opus 4.7 b81dad0467 websearch: tokenizer-aware helper budget, slim comments
B3+B4: replace contextWindow * 0.5 * 4 magic with explicit math —
input_budget_tokens = contextWindow - responseMaxTokens
                                    - SYSTEM_PROMPT_TOKEN_RESERVE,
clamped at MIN_INPUT_TOKEN_BUDGET, converted via CHARS_PER_TOKEN. Now
auto-scales when the helper switches between a 32k qwen and a 200k
Sonnet. Pi-ai exposes no per-model tokenizer; 3.5 chars/token errs short.

Also trim verbose comments added during the prod-ready pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:33:11 +02:00
EislM0203andClaude Opus 4.7 63c3a65e15 websearch: move site-specific extractors into a declarative table
Imperative extractGithubArticle becomes a data table in
site-extractors.ts. Adding a new site is now a data change (one entry,
host regex + selectors) instead of a code change. If a listed site
renames a selector, the extractor returns null and we fall through to
Readability + the density-based fallback — no broken output, just
slightly worse extraction until the table is updated.

The GitHub entry is a 1:1 port of the old code:
- titleSelectors: .gh-header-title, .js-issue-title, release-header
- bodySelectors:  .markdown-body
- exclusiveBodySelectors: .release-main-section .markdown-body
  (covers release pages where many .markdown-body blocks exist)

Live-tested on github PR / discussion / Wikipedia — identical char
counts to pre-refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:26:53 +02:00
EislM0203andClaude Opus 4.7 5f231bc4fc websearch: replace chrome-phrase list with density-based Readability check
The hardcoded CHROME_PHRASE_MARKERS list ("provide feedback", "saved
searches", reddit sidebar copy …) was a maintenance treadmill — every
new site whose chrome happened to contain different stock phrases
needed a new entry. The detection signal was tied to whatever empty-
state copy GitHub or Reddit happened to ship that week.

Replace it with two principled signals:

- If Readability's output is shorter than MIN_READABILITY_CHARS (200),
  it's almost certainly not a real article. Use the body-strip
  fallback.
- If the body-strip fallback is at least FALLBACK_PREFERENCE_RATIO (3×)
  larger than Readability's output, Readability probably picked a
  chrome island. Use the fallback.

Both constants are signal-shaped, not site-shaped — they don't go stale
when a site changes its copy. The hardcoded phrase list is gone.

Net effect on the real-world URLs we've been testing:
- GitHub PR: 14,300 chars (site extractor still short-circuits before
  density — unchanged)
- Wikipedia: 39,518 chars (Readability picks correctly — unchanged)
- Reddit comment thread:  1,846 chars → 35,282 chars (density rule now
  grabs the full thread instead of just the OP — real upgrade)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:24:59 +02:00
EislM0203andClaude Opus 4.7 8216a4dd6d websearch: move tunables into config, helper system prompt into helper-prompt.md
Everything that was a magic number scattered through the module bodies
is now a named config field with a documented default:

- fetchTimeoutMs    (was hardcoded 10_000)
- helperTimeoutMs   (was hardcoded 30_000)
- helperMaxTokens   (was hardcoded 2000)
- cacheTtlMs        (was hardcoded 15min)
- userAgent         (was inlined; lifted to DEFAULT_USER_AGENT)
- sizeCapMultiplier (was hardcoded 10)
- maxRedirects      (was hardcoded 10)

All new fields are optional at the schema level so v1 configs continue
to load with their defaults filled in. fetchUrl now takes an optional
FetchOpts third arg; existing 2-arg callers fall back to module
defaults — tests didn't need rewriting.

Helper SYSTEM_PROMPT moves out of helper-extract.ts and into
extensions/websearch/helper-prompt.md, read at module init via
fileURLToPath(import.meta.url). Lets you iterate on the prompt without
TS edits and makes the actual text easy to diff.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:22:50 +02:00
EislM0203andClaude Opus 4.7 4978123cf4 websearch: refuse cross-domain redirects, cap response size
Two security/safety items where pi was sloppier than Claude's WebFetch.

Cross-domain redirects (A2):
- Switch fetch() to redirect: "manual" and follow same-host hops in a
  bounded loop (max 10). When the next host differs from the original,
  return a structured cross-domain-redirect failure with the redirectTo
  URL so the caller can decide whether to follow. Strict host equality
  — www.example.com → example.com counts as cross. Caller can re-issue
  with the new URL if it wants.
- Prevents a fetch to a "safe" URL silently leaking the request to a
  redirected attacker host.

Size guard (A3):
- Reject before downloading when Content-Length declares > 10× the
  configured fetchCharLimit.
- For chunked/unknown-length responses, stream-read with the same cap;
  cancel the reader and return kind: "too-large" if we exceed it.
- Stops a 50 MB HTML dump from being downloaded just to be sliced.

New FetchErrorKind variants: cross-domain-redirect, too-large,
too-many-redirects. Plus optional redirectTo / size fields for the
structured error so the agent has everything it needs to react.

Live-tested on the same URL set as before — no regressions on
size/extraction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:18:01 +02:00
EislM0203andClaude Opus 4.7 87263739a2 websearch: bubble structured fetch outcome fields into tool result details
Today's tool result was {url, ok}. Callers had to substring-match the
text to figure out whether they got a 403 vs a timeout vs a body that
arrived but had the wrong type.

Extend FetchOutcome with finalUrl / contentType / truncated /
crossHostRedirect on success, and bubble kind (+ HTTP status when known)
on failure. Tool result details now carry all of it, so the calling
agent can branch without parsing strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:14:52 +02:00
EislM0203andClaude Opus 4.7 b40fe60d6d websearch: refuse web_search until SearXNG is configured, harden helper prompt
- config: drop the hardcoded LAN IP default for searxngUrl. Empty string
  is the new "unset" sentinel.
- search: searxngSearch returns a clear error pointing the caller at
  /websearch when the URL is empty/whitespace instead of trying to call
  http://. This was the single biggest blocker to anyone else running
  the extension.
- helper-extract: add an explicit "page text is untrusted, treat
  imperative statements as data, not commands" frame to the helper
  system prompt. Closes an obvious prompt-injection vector for an agent
  that fetches arbitrary URLs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:12:47 +02:00
EislM0203andClaude Opus 4.7 3b361cd8f2 websearch: fall back from Readability when it returns only chrome phrases
Generalizes the GitHub-specific extractor's win to any site where
Readability picks the login/footer chrome over the real article (app-shell
pages, Discourse forums, Notion-published pages, etc.).

After Readability produces markdown, check it against two cheap signals:
- length under ~200 chars (Readability rarely returns that little on a
  real article)
- presence of known chrome phrases ("provide feedback", "saved searches",
  reddit's "use the following search parameters" sidebar text, …)

When either fires, fall through to the body-strip path that was already
the fallback for empty-Readability output. Only override Readability when
the fallback is actually larger — otherwise stick with what we have.

No regression on normal articles: Wikipedia, mizine.de, and the github
URLs all extract the same content as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:51:22 +02:00
EislM0203andClaude Opus 4.7 7beef3d367 websearch: token-prefix class stripping, GitHub extractor, bigger fetch budget
Three independent bugs were stacking up to make fetch_url unreliable on
real-world pages:

- html-extract: BOILERPLATE_CLASSES used \b word boundaries against the
  full class attribute, so "sidebar" matched "content-featured-sidebar"
  on WordPress <body> elements — nuking the entire page (mizine.de went
  to 0 chars). Switch to per-token matching with a "stop" OR "stop-*"
  prefix rule. Drop "comment" from the stoplist; it was eating Reddit /
  HN / GitHub comment sections via parent containers like .commentarea.

- html-extract: add a GitHub-specific extractor that runs before
  Readability for github.com hosts. Readability was picking GitHub's
  "Provide feedback / Sign in" footer chrome as the article on PR /
  issue / discussion pages. New path pulls .gh-header-title +
  .markdown-body elements (works for PRs, issues, discussions, blob
  views, releases).

- config: bump fetchCharLimit default 10000 → 80000. The previous limit
  cut off long doc pages (paperless's configuration.md is ~89k chars
  with the AI section at the very end).

Verified live: mizine.de 0 → 22.9k, github PR 600 → 14.3k, github
discussion 603 → 19k, reddit comments 670 → 1.8k, raw config.md
truncated → full file including AI section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:46:40 +02:00
EislM0203andClaude Opus 4.7 89b7486cd7 Add design spec for local-models → custom-providers rename + kind unification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:18:44 +02:00
EislM0203andClaude Opus 4.7 4350e6fd1b websearch: send browser UA on fetch, broaden helper for non-question prompts
- fetch.ts: set a Firefox-shaped User-Agent + Accept/Accept-Language so
  default-Node-UA filters stop returning 403 on the majority of sites.
- helper-extract.ts: when the prompt is a topic/keyword rather than a
  question, return a structured outline (title + sections + answerable
  questions) instead of guessing at an answer and producing a single
  filler sentence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:15:58 +02:00
EislM0203andClaude Opus 4.7 fd45ef1b5e subagent: drop bundled-agent read-only guards in the /subagents panel
Bundled agents live in <repo>/agents/ — edit and delete now write/remove
those files directly, matching how a package author iterates on their own
shipped agents. The "c" duplicate shortcut is still there for users who
want a user-dir fork instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:17 +02:00
EislM0203andClaude Opus 4.7 8c9b0750b4 modes: add bundled research mode
Pairs with the researcher agent. Tools: web_search, fetch_url, read, grep, find, memory_read.
Read-only with explicit "quote, don't allude" guidance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:12 +02:00
EislM0203andClaude Opus 4.7 96c6bcf337 docker: install runtime deps inside the image, expand .dockerignore
Dockerfile now runs `npm ci --omit=dev` inside /opt/pi-customizations so
v1.1's new runtime deps (linkedom + readability + turndown + turndown-plugin-gfm)
are present — they aren't part of pi's global install.

.dockerignore now excludes more host-only or test-only paths:
- .pi/, .claude/, .worktrees/ (local agent/IDE/git state)
- docs/ (planning docs, not needed in image)
- *.test.ts, **/fixtures/, vitest.config.ts (test code/data, 200 KB of fixtures)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:36:06 +02:00
EislM0203andClaude Opus 4.7 db2a82cfeb agents: researcher — web research agent using web_search + fetch_url
Defines a researcher agent that surveys the open web, fetches relevant pages,
and returns a compressed brief. Uses the v1.1 websearch tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:43 +02:00
EislM0203andClaude Opus 4.7 9d550a7d2d websearch v1.1: linkedom + Readability + turndown pipeline, orchestrateHelper refactor, smoke tests
Replaces the regex-based HTML→Markdown pipeline with a DOM-based one and
extracts helper-orchestration into a pure function so index.ts::execute
becomes a thin coordinator.

- Declare engines.node >= 18 (AbortSignal.timeout requires it)
- Deps: linkedom, @mozilla/readability, turndown, turndown-plugin-gfm
- helper-extract.ts: new orchestrateHelper() owns truncate → extract → hybrid-error envelope
- html-extract.ts: new htmlToReadableMarkdown(html, baseUrl) — parseHTML → stripBoilerplateClasses → Readability → stripChrome → turndown(GFM). Old extractMain/htmlToMarkdown removed.
- index.ts::fetch_url execute delegates to orchestrateHelper; module-scope wrapWarning helper for the two "helper unavailable" branches
- New tests: 3 orchestrateHelper, 9 htmlToReadableMarkdown fixture, 1 real-world MDN smoke, 6 index.test.ts fetch_url execute scenarios. Old 38 regex-pipeline tests removed.
- Total: 193 tests across 20 files (was 220 across 20).

Resolves six known limitations:
- A: word-boundary regex on classes (main-nav-container-info false positive)
- B: AbortSignal.timeout needed Node >= 17.3 with no engines declaration
- C: leaf-only boilerplate stripping let wrapper elements with non-boilerplate children survive
- E: no integration tests for fetch_url::execute
- H: orchestration logic awkwardly inlined in index.ts
- I: brittle regex-based HTML→Markdown

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:38 +02:00
EislM0203andClaude Opus 4.7 437dc7bade Add implementation plan for websearch v1.1 fixes
Eight tasks: deps + engines, orchestrateHelper, new html-extract pipeline
(additive), fetch.ts switch + old code removal, captured fixture smoke
test, index.ts refactor, six smoke tests, final verification. TDD-style
with hand-written HTML fixtures for each named regression (A, C) plus
one real-world capture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:38 +02:00
EislM0203andClaude Opus 4.7 5525fdfd21 Refine websearch v1.1 spec: Readability try/catch, fixture strategy, behavioral acceptance
- htmlToReadableMarkdown catches Readability exceptions and falls through
  to the chrome-stripped body path; without this, parser-throw cases would
  surface as opaque "Could not read response body" errors despite having
  the page in hand.
- Test fixture strategy: hand-written minimal HTML for each named regression
  plus one captured real article. Stops the implementer from picking
  arbitrarily.
- Acceptance criterion 4 replaced: behavioral ("delegates to orchestrateHelper,
  no inline extract logic") instead of cargo-cult line count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:38 +02:00
EislM0203andClaude Opus 4.7 3b4345ea6d Add design spec for websearch v1.1 fixes
Replaces regex-based HTML extraction with linkedom + Mozilla Readability +
turndown, extracts orchestrateHelper from index.ts, declares engines.node
>= 18, and adds smoke tests for fetch_url execute. Resolves issues A, B,
C, E, H, I identified in the helper-model post-implementation review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:38 +02:00
EislM0203andClaude Opus 4.7 41746eea26 websearch: fetch_url helper-model extraction — optional prompt arg summarizes via helper model
Adds an optional `prompt` argument to `fetch_url`. When supplied, the fetched
page is summarized through a helper model with the user's question, and only
the answer is returned. Without prompt, behavior is unchanged.

- helper-extract.ts: truncateForHelperContext, buildMessages, extractAnswer, extractWithHelper (with DI complete())
- SYSTEM_PROMPT for helper guidance
- config.helperModel field (with back-compat) — optional provider+id pair, falls back to active model
- /websearch panel: helper model picker
- index.ts: wire fetch_url to helper-extract when prompt is supplied; fall back to raw page on helper failure (hybrid-error envelope)
- imports consolidated at file tops

Includes design spec and implementation plan under docs/superpowers/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:13:18 +02:00
EislM0203andClaude Opus 4.7 046e694288 websearch: fetch_url extraction overhaul — extractMain + htmlToMarkdown + cache + redirects
Replaces the simple htmlToText path with a regex-based extraction pipeline,
adds a 15-min in-memory cache, surfaces cross-host redirects in the output,
and adds url-rewrite helpers for github blob and reddit URLs.

- extractMain: content-root selection + nav/header/footer/aside stripping + boilerplate class strip
- htmlToMarkdown: hand-rolled HTML→Markdown converter with \\x00 sentinel for code-block placeholders
- fetchUrl: 15-min in-memory cache, cross-host redirect surfacing, session-start cache clear
- fetch_url tool: reserved prompt arg in signature, refreshed description
- url-rewrite: github blob → raw, old.reddit normalization

Includes design spec and implementation plan under docs/superpowers/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:11:34 +02:00
EislM0203andClaude Opus 4.7 b45c2d1410 websearch: add sandbox-mode toggle for fetch_url egress hints
When sandbox mode is on, fetch_url failures with kind="network"
(connection refused, DNS, timeout — the egress-denial signatures) get
a trailer pointing the model at the most likely cause: an unapproved
egress rule for the host. HTTP-level failures and body-read errors are
unaffected, since those indicate the egress actually worked.

Toggle lives in the /websearch panel (key: b). Config field is
optional in older files, defaulting to false, so existing
websearch.json keeps loading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:29:54 +02:00
EislM0203andClaude Opus 4.7 89de94c44c Add /mode extension — tool-mode switcher for small local models
Collapses the agent's active tool schema to a focused subset for the task
at hand, and optionally appends a short system-prompt nudge. The win for
small local models: a 4-tool schema produces dramatically more reliable
tool calls than a 20-tool schema, and a one-paragraph nudge keeps the
model on-task.

Commands:
- /mode <name>   switch (bundled or user mode)
- /mode reset    restore the default tool set captured at session_start
- /mode          open the management panel

Bundled, read-only modes (extensions/modes/defaults.ts):
- plan   read-only investigation (read/ls/find/grep/web_search/memory_read)
- edit   code editing without bash (read/write/edit/ls/grep/find)
- debug  read + bash; investigate without writing
- chat   no tools; answer from knowledge

User modes live as <name>.md under ~/.pi/agent/modes/ with YAML
frontmatter (name, description, tools) + body (the prompt nudge). The
/mode panel mirrors /subagents and /memory: list / view / new / edit /
delete / duplicate / switch. Bundled modes reject edit/delete; broken
files surface with a [broken] tag and only support delete. Tool-name
validation is lenient at save time and at switch time: unknown tools
trigger a warning notify but the save (or the switch) proceeds.

File layout:
- store.ts      types, file IO, validate-name, serialize/parse
- defaults.ts   bundled Mode[] (data, no IO)
- apply.ts      pure: intersect tools, append prompt nudge
- panel.ts      /mode TUI panel
- index.ts      wires command, before_agent_start nudge, session_start capture

State is session-only: each new session re-captures the default tool set
and clears the active mode. Status line shows `mode: <name>` while active.

Tests: 20 new vitest cases — store IO (round-trip, dir resolution,
discover, save/delete), bundled defaults well-formedness, applyMode
intersection + dropped-tools tracking + prompt-append shape + mode-order
preservation.

Spec: docs/superpowers/specs/2026-05-16-modes-design.md
Plan: docs/superpowers/plans/2026-05-16-modes-extension.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:24:56 +02:00
EislM0203andClaude Opus 4.7 be4059653f Add implementation plan for /mode extension
8-task TDD plan: store types + IO (tasks 1-3), bundled defaults (4),
pure switching logic (5), TUI panel (6), wiring (7), verification (8).
Mirrors patterns from extensions/memory and extensions/subagent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:55:59 +02:00
EislM0203andClaude Opus 4.7 9003a1823e Add design spec for /mode extension
A tool-mode switcher tailored to small local models: collapse the agent's
schema to a focused 3-7 tools per mode and append a task-specific prompt
nudge. Bundled defaults (plan/edit/debug/chat) are read-only; user modes
live as one markdown file per mode in ~/.pi/agent/modes/ and are managed
via the /mode panel (mirroring /subagents). /mode <name> switches; /mode
reset restores defaults; /mode opens the panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:49:51 +02:00
EislM0203andClaude Opus 4.7 9a6a72044a websearch: /websearch <query> dispatches a search instead of opening config
Mirrors the /remember <text> pattern: with an arg, sends a templated
user message asking the agent to call web_search. Without args, opens
the config panel as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:41 +02:00
EislM0203andClaude Opus 4.7 3a545ef464 Add /memory extension — persistent file-based memory across sessions
A new extension under extensions/memory/ that gives the agent a
cross-session memory, modeled on the Claude Code auto-memory system.

How it works:
- before_agent_start handler discovers memories from two roots
  (~/.pi/agent/memory/ for global, <cwd>/.pi/memory/ for project),
  builds a typed index grouped by user / feedback / project / reference,
  and appends it to the system prompt every turn (zero tokens when both
  stores are empty).
- memory_save tool: agent writes/updates an entry; same-name in same
  scope under a different type triggers a move with old-file cleanup.
- memory_read tool: agent fetches one entry's body by name; both scopes
  returned when ambiguous.
- /memory command: TUI panel for list/view/create/edit/delete/duplicate.
  Mirrors /subagents bindings; broken entries surfaced with [broken] tag.
- /remember <text>: dispatches a templated user message so the agent
  classifies + writes in one turn.
- /remember (no arg): opens the same form as /memory's "new" — direct
  write, no model involvement.

File layout (mirrors existing extensions/subagent/ decomposition):
- store.ts: types, file IO, scope resolution, save/discover/delete
- inject.ts: buildMemoryIndex (system-prompt block builder)
- tools.ts: pure tool handlers + typebox schemas (no pi runtime)
- panel.ts: /memory + /remember form TUI
- index.ts: wires tools, commands, and the before_agent_start handler

Tests: 30 new vitest cases cover store IO end-to-end (round-trip,
scope merging, broken-entry surfacing, multi-stale cleanup, save
actions), the index-block builder (empty case, grouping, ordering),
and tool-handler happy paths + invalid-name rejection.

Spec: docs/superpowers/specs/2026-05-15-memory-design.md
Plan: docs/superpowers/plans/2026-05-15-memory-extension.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 12:21:59 +02:00
EislM0203andClaude Opus 4.7 13c9d039d0 Add implementation plan for memory extension
9-task TDD plan covering store, inject, tools, panel, wiring, and
verification. Mirrors patterns from extensions/subagent and
extensions/local-models. Tests for store (round-trip, scope resolution,
discover, save/move), inject (block format, ordering, empty case), and
tool handlers (success + invalid name + multi-scope).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 07:47:38 +02:00
EislM0203andClaude Opus 4.7 574d5bd44d Add design spec for memory extension
Persistent file-based memory across sessions. Index-only autoload via
before_agent_start; full entries fetched on demand. Both global and project
scope. Typed categories (user/feedback/project/reference). /remember
captures (LLM-assisted with arg, panel form without). /memory panel for
direct management. Mirrors the Claude Code auto-memory shape, adapted to
pi's extension API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 07:13:22 +02:00
EislM0203andClaude Opus 4.7 86afd57e29 Add /subagents definition management panel
Origin-aware agent discovery (bundled/user/project, non-deduplicated) plus
serialize/write/delete helpers in agents.ts, a new panel.ts implementing the
/subagents ctx.ui.custom panel (list, detail view, create/edit/delete/
duplicate — bundled agents read-only), and the /subagents command in
index.ts. Frontmatter is collected via ctx.ui.input, the system prompt body
via the built-in ctx.ui.editor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:34:52 +02:00
EislM0203andClaude Opus 4.7 923ce33d5d Add implementation plan for subagent definition management panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:33:08 +02:00
EislM0203andClaude Opus 4.7 6ba6cbfa14 Use built-in ctx.ui.editor for subagent system prompt body
Replaces the embedded pi-tui Editor sub-panel approach with pi's
built-in multi-line editor dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:24:02 +02:00
EislM0203 b2d10d868c Merge branch 'feat/websearch-searxng' 2026-05-14 21:15:56 +02:00
EislM0203andClaude Opus 4.7 b8dbb25c17 Add /subagent-runs live monitoring panel
Extract run execution from index.ts into runner.ts, add an in-memory
RunRegistry shared between the subagent tool and a new /subagent-runs
command, give each run its own AbortController for per-run cancellation,
and add the runs-panel.ts monitor with a live-refresh timer. Also adds
TypeScript type-checking (tsconfig.json + typecheck script) so the
refactor has an automated safety net.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:14:46 +02:00
EislM0203 56cf48ca18 Report a corrupt websearch.json at session start 2026-05-14 21:13:41 +02:00
EislM0203andClaude Opus 4.7 1921bf1a82 Add implementation plan for subagent live-runs monitoring panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:11:34 +02:00
EislM0203 f565800f06 Register web_search and fetch_url tools and /websearch command 2026-05-14 21:08:50 +02:00
EislM0203 42623f5f8b Clear stale test result on save and warn on empty URL input 2026-05-14 21:07:32 +02:00
EislM0203 a490864f93 Add /websearch TUI configuration panel 2026-05-14 21:00:01 +02:00
EislM0203 8c122260c5 Guard response body reads in fetchUrl to preserve never-throws contract 2026-05-14 20:53:17 +02:00
EislM0203 b0be7c75ef Add URL fetch and HTML-to-text helper 2026-05-14 20:44:33 +02:00
EislM0203 815cc8d908 Guard res.text() in searxngSearch to preserve never-throws contract 2026-05-14 20:33:13 +02:00
EislM0203 0a92f11fd1 Add SearXNG search query and result parsing 2026-05-14 20:28:07 +02:00
EislM0203andClaude Opus 4.7 5062bf80ef Add design spec for subagent live-runs monitoring panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 20:24:59 +02:00
EislM0203 7d762461f3 Validate config version in loadConfig 2026-05-14 20:24:49 +02:00
EislM0203 744e3cab0a Add websearch config load/save 2026-05-14 20:05:04 +02:00
EislM0203 86b697c0ae Add implementation plan for SearXNG websearch extension 2026-05-14 19:08:34 +02:00
EislM0203andClaude Opus 4.7 2e60232f86 Add design spec for subagent TUI management panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:07:04 +02:00
EislM0203 818d045bbc Add design spec for SearXNG websearch extension 2026-05-14 19:02:37 +02:00
EislM0203 744628c571 Ignore .worktrees/ directory 2026-05-14 19:00:49 +02:00
EislM0203 fe2de1b292 Merge feat/llama-swap-context-probe: real context for loaded llama-swap models
discovery.ts detects a llama-swap nativeUrl (responds to /running) and
reads each loaded model's real context window by parsing -c/--ctx-size
from its command line; not-loaded models keep defaultCtx. The llamacpp
seed entry gains nativeUrl.
2026-05-14 18:09:40 +02:00
EislM0203 1d69725c79 Probe loaded llama-swap models for real context windows 2026-05-14 16:58:59 +02:00
EislM0203 046b1421fd Add parseContextFromCmd helper for llama-swap context probing 2026-05-14 16:44:20 +02:00
EislM0203andClaude Opus 4.7 dfc3693ea7 Add implementation plan for llama-swap context probe
3 tasks: parseContextFromCmd helper (TDD), llama-swap probe in
discoverProvider + config seed update, end-to-end verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:23:37 +02:00
EislM0203andClaude Opus 4.7 340d8f9963 Add Dockerfile for running pi customizations in an OpenShell sandbox
Builds a node:22-slim image with pi installed globally and this package
registered via a local-path `pi install`. Meets OpenShell BYOC requirements:
sandbox user at uid/gid 1000660000, iproute2/iptables, writable /sandbox workdir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:19:42 +02:00
EislM0203andClaude Opus 4.7 f1a915eb55 Add design spec for llama-swap context probe
discovery.ts gains a llama-swap-aware probe: GET <nativeUrl>/running,
parse -c/--ctx-size from each running model's cmd, use the real context
for loaded models and defaultCtx for the rest. Detection by path
convention on nativeUrl; manual re-probe via the panel's existing 'r'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:15:27 +02:00
EislM0203 c336ce414f Merge feat/local-models-tui: editable local model providers via /local-models
Restructures the local-models extension into config.ts / discovery.ts /
panel.ts / index.ts. Providers live in ~/.pi/agent/local-models.json
(seeded on first run); a /local-models TUI panel adds/edits/removes/
refreshes them, re-registering with pi live (no /reload).
2026-05-14 15:58:58 +02:00
EislM0203andClaude Opus 4.7 c574cce58d Add design spec for OpenShell sandbox Dockerfile
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:55:54 +02:00
EislM0203 922ddabb23 Wire /local-models command into the extension 2026-05-14 15:47:50 +02:00
EislM0203 25ea4445b2 Add local-models interactive panel: list, add/edit/remove/refresh 2026-05-14 15:41:53 +02:00
EislM0203andClaude Sonnet 4.6 a23b8ca19b Rewrite local-models index: config-driven startup + live helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:34:15 +02:00
EislM0203andClaude Sonnet 4.6 abedfc090a Add local-models discovery module (extracted from index.ts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:26:25 +02:00
EislM0203 d6b5c67262 Classify unreadable config file as corrupt, not missing; add edit/dir tests 2026-05-14 15:24:26 +02:00
EislM0203andClaude Sonnet 4.6 36553cc2cb Add local-models config: types, load/save, CRUD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:17:40 +02:00
EislM0203andClaude Opus 4.7 810aa7b2d1 Add implementation plan for local-models TUI configuration
6 tasks: config.ts (TDD), discovery.ts extraction (TDD), index.ts
rewrite, panel.ts, /local-models command wiring, e2e verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:09:59 +02:00
EislM0203andClaude Opus 4.7 b5bcd5b5b3 Add design spec for local-models TUI configuration
Make the local-models extension's providers editable via a /local-models
interactive panel, persisted to ~/.pi/agent/local-models.json, with live
register/unregister so edits take effect without /reload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:56:01 +02:00
EislM0203andClaude Opus 4.7 60a1fda889 Wrap /hosts key hints to terminal width so no key is truncated
The hint line was a single long string that pi truncated to the panel
width, hiding "a add / e edit / d remove / i import / Esc close" on
narrower terminals. Now the hints pack into as many lines as needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:43:52 +02:00
EislM0203andClaude Opus 4.7 a60c8a6350 Make /hosts import add the host directly instead of routing through add
Selecting an entry from ~/.ssh/config now adds it to the registry
immediately using the parsed values, with no field prompts. The old
flow re-prompted every field because ctx.ui.input only takes a
placeholder (not an editable default), so the "pre-filled" import was
indistinguishable from a blank Add. Use 'e' to tweak a field afterward.

Also collapses the duplicate label-list derivation into one array.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:39:29 +02:00