npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pi-roundtable-coding

v0.32.0

Published

Repository shelves and owner-approved Pi coding workers for pi-roundtable

Readme

pi-roundtable-coding

A repository shelf and an out-of-process Pi coding desk for pi-roundtable. Agents and owner sessions get five repository tools; coding jobs commit locally and report back, while shipping stays behind the owner's approval. This package is a reference for the plugin guide's Helpers for a Pi session of your own.

Requirements

  • Bun 1.4.4 or newer on a POSIX host (Linux or macOS).
  • pi-roundtable >=0.32.0 <0.33.0 as a peer dependency; this package uses only its public main, kit and testing entries.
  • Pi >=1.0.0 <2, shared with the host's core dependencies. A core-only host still pinned to Pi 0.99.x must update its Pi dependencies before adding coding, so cross-package extension and session types resolve to one Pi version.
  • Git, plus GitHub CLI (gh) for the default clone implementation.
  • A Git host token with read access to repositories being cloned and write access to those being pushed, or a host login holding that token.
  • Pi model credentials available through the host's Pi login or the provider's environment variables.

The default clone uses gh repo clone owner/repo and inherits GH_TOKEN or the host's gh auth login credentials. Configure Git's credential helper for later git fetch and git push (for GitHub, gh auth setup-git). Use a least-privilege token and restrict its repositories; tokens are never package options, tool arguments or remote URLs. The package does not print subprocess output or Git authentication diagnostics and never logs credentials. The coding worker inherits the host environment and can read files accessible to that host user: do not give it credentials you would not give a trusted coding agent. A custom clone(repo, destination) can use another Git host's CLI, credential helper or SSH login, and must create a Git clone with an origin remote. No network calls are needed by the offline test suite.

Setup

bun add pi-roundtable-coding pi-roundtable
// roundtable.config.ts
import { coding } from "pi-roundtable-coding";

export default {
  // ...the settings created by pi-roundtable init...
  plugins: [
    coding({
      shelfDir: "/srv/roundtable/repos",
      model: "anthropic/claude-sonnet-4-6",
      thinking: "medium",
      timeoutMs: 60 * 60_000,
      // Default []: every repo_push is held for approval.
      ownerRepos: [],
    }),
  ],
};

The model is an explicit provider/model-id available on this host, not selected by this package. agentDir defaults to Pi's host login directory and controls its auth.json and models.json locations. No automatic extension, skill, prompt-template or theme discovery is enabled for workers. Only repository context files within the clone are included as standing instructions.

To load installed Pi extension packages (for example a model provider), pass their absolute package directories in workerPackages. Resolve them from your configuration rather than from a core checkout:

import { packageDir } from "pi-roundtable/kit";

const workerPackages = [packageDir("your-provider-package", import.meta.url)];

Install the provider package on the host first and pass workerPackages to coding. Workers activate only SHELL_TOOLS (bash, read, edit, write); extension packages are trusted executable code, not a way to grant extra model tools.

Principal migration in 0.9

Use the same lockstep core and package release; Migrating to 0.9 covers the host changes. Owner skills depend on a private conversation's principal holding a lasting owner role, not kind: "owner" or an assumed tier. Pass the router-resolved speaker to any custom turn or report integration, and direct personal reports only to that principal's own channel. System report turns cannot call repo_task; do the work in the turn or hand it to a person.

Tools and flow

| Tool | Parameters | Behavior | | --- | --- | --- | | repo_add | repo | Clone owner/repo into shelfDir/owner/repo; refuse invalid names and existing clones. | | repo_list | fetch? | List paths, branches, upstream ahead/behind counts, dirty files, last commits, prose summaries, CI hints and linked skills. | | repo_task | repo, task, skills? | Start a background worker in its own worktree, or queue it when every worker slot is taken, returning its job number (and queue position) immediately. The host's own turns, such as a report's, may not. | | repo_change_report | repo | Fetch and post the commits and diffstat ahead of the default branch; return its full SHA. | | repo_push | repo, sha | Push the exact reported SHA to the reported default branch, without force, after the hold gate permits it. |

All five tools have minTier: "owner" and are contributed through defineTool for agent and owner session selections. The operator remains responsible for any tool-tier overrides. A custom owner claim with a restricted ToolSelection must include the exported REPO_TOOLS names; include skill_list separately when the host skills addon is enabled. The host's skills addon resolves explicit skills, or the calling agent's carried skills when omitted; an owner call carries none unless it names them. Missing or unknown skills refuse the task by default; requesting skills while the addon is off also refuses it. skipUnavailableCarriedSkills: true opts into skipping unavailable implicit skills and disclosing them in the task-start text or through presentation.taskStarted. Explicit skill requests still refuse missing or unknown skills. A private conversation whose person holds the owner role, as IDENTITY.tierOf tells, gets the public skillListExtension, whatever its kind. A member's private conversation and any shared conversation, an owner's included, get none; so does every conversation when the identity service is absent or its lookup fails. Agent sessions keep the core's existing skill_list instead of registering it twice. The role is checked when the session is built and again before every extra skill_list execution; a removed role or failed lookup immediately refuses the call, even in a reused session. A newly granted role makes the extra tool available once the session is rebuilt.

A worker reads the repository's instructions, edits and checks the work, and commits using repository conventions. Each worker runs in its own linked worktree on its own branch, worker/<job> (with a suffix when a kept branch already has that name), cut from the clone's HEAD when it starts, so several workers can work on one repository at once. Worktrees live under shelfDir/.worktrees/<owner>/<repo>/; the shelf lists no repository there, and owners starting with a dot are refused as repository names. The managed clone stays the place reports and pushes ship from.

When a worker finishes, its branch is rebased onto the clone's current HEAD inside its worktree and the clone is fast-forwarded to it; the worktree and branch are then removed, and repo_change_report ships the result as before. A rebase conflict is aborted in the worktree, so the clone is never left mid-rebase; the branch and worktree are kept and the report names the conflicting files, the branch and the worktree path. A clone with uncommitted changes at that moment, or a worktree the worker left uncommitted changes in, is not touched: the report says why and keeps the branch. A worker that committed nothing leaves no worktree or branch behind. Integrations of one clone run one at a time, and wait for a report or push of it in progress. Reports and pushes refuse a repository only while an integration is changing its clone, not while workers run in their worktrees; the desk also refuses a second concurrent report or push of a repository and new coding tasks for it while one runs.

At most maxWorkers workers (default 4) run at once on the host, across repositories and channels. Past that a task waits in a first-in, first-out queue of at most maxQueued tasks (default 16), and repo_task answers at once with its queue position; one more is refused. A queued task starts when a worker slot frees. On host stop queued tasks are dropped and each gets a report saying it never ran; the shutdown drain waits only for running workers. The worker's report includes its final answer, declined or unanswered actions, calls approved by yolo mode, what became of its branch, branch, HEAD, new commits and uncommitted files. Failures and timeouts also report the final Git state when it can be read. By default reports are sent to the caller's surface; onResult(result) can instead enqueue a background conversation turn through your host integration. Dates are formatted using context.env.timeZone, not a process-wide zone.

Shipping is separate from coding:

  1. Review the worker's changes and verification evidence.
  2. Call repo_change_report; the clone must be clean, ahead of origin/HEAD, and missing none of its commits.
  3. Call repo_push with the full SHA from that report.
  4. The host holds the push and asks the owner before running it.

ownerRepos is an exact list of owner/repo names, not a prefix or wildcard; its default is empty. Listed repositories skip the plugin's repo_push hold, though other host hold rules can still hold the call. A trusted isOwnerRepo(repo) host policy can grant additional exemptions, including repositories created after startup; only literal true grants one. The package validates the repository name before consulting it. Never derive this policy from model input, clone files, or remote repository instructions. Neither exemption bypasses shell pushes, risky worker actions, or other linked host rules. The report and approval card name the full SHA, current default branch and credential-free push destination. The clone must have exactly one push destination; HTTP remote URLs with user information, query strings or fragments are refused before fetching. A changed HEAD, dirty clone, changed remote default branch, changed push destination, missing report or non-fast-forward remote causes refusal. Report receipts live in memory, so request a fresh report after restarting the host.

Approvals and work time

Every worker tool call crosses a private Bun IPC channel to the parent process before execution. The parent consults shellHoldRule, additional holds, and the host's linked hold rules; a recognized risky action asks the owner's surface prompts using approvalCard. When threads is configured, progress and cards stay in a thread opened under the resolved run's origin; if no thread can open, calls remain held rather than falling back to another channel's approval cards. Without threads, the resolved report channel's prompts are used. threadText controls the initial post, held-action post, approval title and final report; the thread is archived before onResult is called, and a failed thread post/close does not discard result delivery. While it works, the worker posts its progress into its thread, as the host's own turns do: text of interimPrimaryChars (400) characters or more, or written with a Markdown heading, list, table or code fence, as ordinary messages, and short narration with the tools it called in one small-text progress message edited in place. The thread's cards come after the text written before them, the final report is posted once, and a failed progress post or edit is logged and never fails the job. interimText: "off" posts only the task, the held-action notices, the cards and the report; a thread whose host cannot edit its posts gets no progress either. promptSlot tracks these cards and workTimeout excludes their waiting time from the worker's budget (one hour by default).

Yolo and long-running modes

With modes (the host's MODES service, context.services.find(MODES)) and autonomy (the same values as the host's autonomy config), a worker follows the yolo and long-running modes of the conversation whose turn started it, read live: switching a mode in that conversation applies to its running workers at once. They act only when an owner other than the host's own principal spoke that turn, so a worker gains nothing its starter could not turn on. In yolo mode a held call is approved without a card exactly as in the conversation: it needs hold classes, and none of them may be carded by autonomy.yolo.card, by YOLO_CARDED_SHELL_CLASSES unless autonomy.yolo.allow names it, or be an unclassed rule allow leaves out; push-class holds that the host cards still stop on a card. The worker reports the classes of every check that holds a call: the shell rule's and the host hold check's classesOf; a check that holds a call without classes leaves it unclassed, so a card decides. Each approval is told to modes.autoApproved with the starter's channel and listed under "Approved by yolo mode" in the report. In long-running mode a worker that reaches timeoutMs goes on, looking again at the mode each minute, up to autonomy.longRunningMaxMs of work in all (four hours by default). Without modes every held call asks a card and the worker stops at timeoutMs. timeoutMs must be positive and finite, and at most 2,147,483,647 ms to fit the host timer. A worker's card has no grace period and no deadline: the worker waits for the answer until it is stopped, and the waiting does not count against its time. An approved call runs; a declined, missing or failed card blocks it and instructs the worker not to retry or work around the refusal. Unapproved actions appear in the report rather than running later automatically. By default worker answers are capped at 20,000 characters and the Held list keeps ten entries of at most 1,000 characters each, with explicit truncation and omission notices; limits changes each bound. A worker failure carries its exit category and numeric code; git and gh stderr and the worker's own error text reach the report only after scrubDiagnostic masks credentials, and are cut at diagnosticChars. Card expiration is determined by the host's surface implementation, not this package.

The worker runs in a fresh Bun child process with an unsaved Pi session and uses the public runWorkerTask helper. Timeout and shutdown kill its POSIX process group and wait for exit; normal completion also removes shell descendants left in that group. The service advertises running jobs through busy() so the host can drain them before shutdown; queued tasks are not among them. CodingDesk.busy() lists running and queued jobs, marking each queued one with its position, and queuedChannels() names the channels of queued tasks. Jobs are in memory; they do not resume after a crash. The injectable worker runner must observe aborts and settle its run; this is a trusted test/integration seam.

Security model

This is a host coding desk, not a sandbox. The worker can run local checks, read the host's accessible files, use the network and commit changes. Its standing instructions delegate shipping to the calling agent; repository tools and extra package tools are not activated inside the worker. repo_push is the supported shipping route and always uses a previously reported full SHA and a non-force push. The public shell rule holds recognized shell pushes, destructive commands, privileged commands and recognized writes outside the configured worker workspace (the clone by default), even for owner-owned repositories. It is a heuristic guard: scripts, interpreters, symlinks, Git hooks, package extensions and detached grandchildren are not an OS isolation boundary. A trusted package or arbitrary shell program can bypass heuristic detection; only run code and repositories you trust, under a dedicated low-privilege host user. Use an isolated container or VM when host access is unacceptable.

The shelf validates names with the public checkRepoName helper and checks real paths before operating on a clone, refusing paths that escape the shelf or Git directories that escape a clone. Metadata discovery skips symlinks, directories and special files, reads at most 64 KiB per file, and refuses symlinked workflow directories. Git commands use argument arrays, not interpolated shell commands. Host-side Git operations disable repository hooks and fsmonitor; the coding worker's own Git operations retain repository conventions. Local Git credential helpers and other executable Git configuration still run as the host user and can be written by a worker, so repository configuration is a trusted boundary, not an approval boundary. Keep external processes from changing shelf paths or Git refs during approval and push; these checks do not serialize unrelated host writers. Model output, diffs and tool inputs can contain sensitive repository content, so report channels and approval cards must remain owner-trusted.

API and options

coding(options) is the plugin factory. The CODING service exposes shelf: RepoShelf and desk: CodingDesk for trusted owner integrations; direct service calls are not the host tool approval gate.

| Option | Default | Purpose | | --- | --- | --- | | shelfDir | required | Managed clone directory. | | model | required | Worker provider/model ID. | | thinking | medium | Pi thinking level. | | ownerRepos | [] | Exact push-hold exemptions. | | isOwnerRepo | none | Trusted host policy for additional push-hold exemptions; must return literal true. | | pushHoldText | repository, sha, target and branch | Trusted wording for the approval card of a held repo_push; it never changes who is held. | | adoptClones | [] | Startup { from, repo } moves of standalone clones; existing shelf destinations are never replaced. | | resolveRun | configured model/thinking, caller channel | Per-caller model, thinking, report channel and optional thread origin. | | postChangeReport | caller surface reply | Post the record using the calling identity and channel. | | presentation | English package text | Separate change-report post/result text, task-start/omitted-skill wording, queued-task wording (taskQueued(job, position, skipped)), and the repo_list result (list(repos, { shelfDir, fetched })). | | toolText | package wording | Trusted description and argument descriptions for each repository tool, as { repo_task: { description, parameters: { task: "…" } } }; it never changes a tool's arguments or approval rules. | | threads | none | Public DispatchThreads-compatible progress and approval thread port. | | threadText | English package text | Thread introduction, held-action notice, approval title and final report. | | interimText | "on" | "off" keeps the worker's progress out of its thread; pass the host config's interimText to follow it. | | interimPrimaryChars | 400 | Length from which a worker's intermediate text is posted as its own message. | | workerWorkspace | individual clone | Trusted shell-policy write boundary; not an OS sandbox. | | workerPrompt | generic worker instructions | Trusted standing prompt replacement; it cannot bypass approval or cleanup. | | workerBlockText | "The owner declined / has not approved this call. Do not retry it or work around it; list it under Held in your report." | Trusted wording of what the worker reads when a call is declined or held, from (answer, action); it never changes who is held, and the desk lists only unanswered calls as held. | | limits | 20,000 report characters, 10 held entries of 1,000 characters | { reportChars?, heldEntries?, heldChars? }: what a report keeps of a long run; each is a whole number of at least 1, or Infinity for no bound. | | diagnosticChars | 600 | Longest git, gh and worker error text kept in a failure; a whole number of at least 1, or Infinity. | | skipUnavailableCarriedSkills | false | Skip and disclose unavailable implicit skills; explicit skill requests still refuse them. | | workerPackages | [] | Absolute installed extension paths. | | agentDir | Pi host directory | Pi credentials and model configuration. | | timeoutMs | 3600000 | Work-time limit in ms, excluding owner wait; 0 < value <= 2147483647. | | maxWorkers | 4 | Workers running at once on the host; a whole number of at least 1. | | maxQueued | 16 | Tasks waiting for a worker slot; a whole number of at least 0. | | modes | none | The host's conversation modes (peek, optional autoApproved), for workers to follow their starter's yolo and long-running modes. | | autonomy | { yolo: { allow: [], card: [] }, longRunningMaxMs: 14400000 } | The host's resolved autonomy limits those modes follow. | | holds | none | Additional worker hold check. | | clone | ghClone | Git-host clone implementation. | | worker | PiCodingWorker | Trusted worker runner replacement. | | onResult | surface reply | Report delivery integration. |

RepoShelf, CodingDesk, PiCodingWorker, CodingWorkerFailure, REPO_TOOLS, codingReport, integrationText, reportPost, WORKTREES_DIR and their option/result types are exported for testing and host integrations. CodingResult.integration tells what became of the worker's branch (merged, nothing, conflict with its files, or refused with the reason) and CodingResult.autoApproved the calls yolo mode approved; CodingJob.starter names the conversation and speaker whose modes the worker follows, and CodingJob.branch its branch. resolveRun(turn) receives the public ToolTurn, including the calling agent scope and channel. It returns { model, thinking, channel, origin? }; skill selection still follows the caller, not the resolved report destination. A resolveRun or postChangeReport that sends a person's report away from the calling channel sends it to that person's own direct channel, context.directChannels.reach(turn.speaker.principalId), and throws ToolRefusal when none reaches them. It never falls back to the primary owner's channel: on a host serving more than one person, that channel would carry someone else's repository work. onResult receives those fields plus loaded skillNames and the optional progress thread in result.job, so a host can enqueue its own localized conversation turn. adoptClones and RepoShelf.adopt keep existing clone contents and path layout without database migrations. No database or Discord-specific types are required.

Development and publishing

This package lives in packages/coding in the pi-roundtable workspace. Run these commands from the repository root:

bun install --frozen-lockfile
bun run --cwd packages/coding test
bun run --cwd packages/coding typecheck
bun run --cwd packages/coding lint

Tests use temporary bare Git origins, testPlugin, fake workers and a real child Pi session with an offline faux provider. They cover cloning/listing, reports, push holds and approved pushes, owner exceptions, work-time exclusion, bad names, closed approval paths, child-process exit, parallel workers merging into one clone, kept conflicts, the worker cap and queue, and yolo and long-running modes. CI runs those checks. The shared publish.yml workflow repeats them, checks that every workspace version and core peer range match the single v* tag, then uses npm trusted publishing with provenance. The owner performs the initial npm publication and configures this package's trust against publish.yml; no separate repository or package-specific tag is needed.

License

MIT; see LICENSE.