@astrofoundry/pi-astro 0.32.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,7 @@ You are the Arcane specialist.
45
45
  const minimal = parseAgentFile("---\nname: scout\ndescription: Recon\n---\nBody", "/a.md", "user");
46
46
  expect(minimal).toMatchObject({ tools: undefined, skills: [], systemPromptMode: "replace", inheritProjectContext: false, inheritSkills: false });
47
47
  expect(minimal?.maxSubagentDepth).toBeUndefined();
48
+ expect(parseAgentFile("---\nname: reviewer\ndescription: d\ntools: none\n---\nBody", "/r.md", "user")?.tools).toEqual([]);
48
49
  expect(parseAgentFile("---\nname: x\n---\nBody", "/b.md", "user")).toBeNull();
49
50
  expect(parseAgentFile("---\nname: 'bad name!'\ndescription: d\n---\n", "/c.md", "user")).toBeNull();
50
51
  });
@@ -14,6 +14,7 @@ export interface AgentConfig {
14
14
  /** Name callers use: `astro.<name>` for bundled agents, `<name>` otherwise. */
15
15
  name: string;
16
16
  description: string;
17
+ /** Strict allowlist; undefined means Pi's defaults and an empty list means no tools at all (`tools: none`). */
17
18
  tools?: string[];
18
19
  skills: string[];
19
20
  model?: string;
@@ -101,7 +102,7 @@ export function parseAgentFile(content: string, filePath: string, source: AgentS
101
102
  return {
102
103
  name: source === "bundled" ? `${BUNDLED_NAMESPACE}.${localName}` : localName,
103
104
  description: frontmatter.description.trim(),
104
- tools: tools.length > 0 ? tools : undefined,
105
+ tools: tools.length === 1 && tools[0] === "none" ? [] : tools.length > 0 ? tools : undefined,
105
106
  skills: parseList(frontmatter.skills),
106
107
  model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
107
108
  thinking: typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
@@ -53,6 +53,12 @@ describe("child arguments", () => {
53
53
  expect(args).toEqual(["--mode", "json", "-p", "--no-session", "--model", "anthropic/claude-x", "--thinking", "high", "--append-system-prompt", "/tmp/p.md", "--", "Task: t"]);
54
54
  });
55
55
 
56
+ it("disables every tool for an agent declared with tools: none", () => {
57
+ const args = buildChildArgs({ agent: { ...agent, tools: [] }, task: "t", promptFile: null, defaults: {} });
58
+ expect(args).toContain("--no-tools");
59
+ expect(args).not.toContain("--tools");
60
+ });
61
+
56
62
  it("does not pass the parent's thinking level to an agent that pins its own model", () => {
57
63
  const pinned: AgentConfig = { ...agent, model: "m" };
58
64
  expect(buildChildArgs({ agent: pinned, task: "t", promptFile: null, defaults: { thinkingLevel: "high" } })).not.toContain("--thinking");
@@ -104,7 +104,8 @@ export function buildChildArgs({ agent, task, promptFile, defaults, session, fil
104
104
  if (model) args.push("--model", model);
105
105
  const thinking = agent.thinking ?? (agent.model ? undefined : defaults.thinkingLevel);
106
106
  if (thinking) args.push("--thinking", thinking);
107
- if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
107
+ if (agent.tools?.length === 0) args.push("--no-tools");
108
+ else if (agent.tools) args.push("--tools", agent.tools.join(","));
108
109
  if (!agent.inheritProjectContext) args.push("--no-context-files");
109
110
  if (!agent.inheritSkills) args.push("--no-skills");
110
111
  if (promptFile) args.push(agent.systemPromptMode === "append" ? "--append-system-prompt" : "--system-prompt", promptFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.32.0",
3
+ "version": "0.33.1",
4
4
  "description": "Personal pi customizations (extensions, subagents, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -49,10 +49,10 @@
49
49
  "typebox": "*"
50
50
  },
51
51
  "devDependencies": {
52
- "@earendil-works/pi-agent-core": "^0.85.1",
53
- "@earendil-works/pi-ai": "^0.85.1",
54
- "@earendil-works/pi-coding-agent": "^0.85.1",
55
- "@earendil-works/pi-tui": "^0.85.1",
52
+ "@earendil-works/pi-agent-core": "^1.3.0",
53
+ "@earendil-works/pi-ai": "^1.3.0",
54
+ "@earendil-works/pi-coding-agent": "^1.3.0",
55
+ "@earendil-works/pi-tui": "^1.3.0",
56
56
  "@eslint/js": "^10.0.1",
57
57
  "@types/node": "^26.5.1",
58
58
  "@vitest/coverage-v8": "^5.0.0",
@@ -133,7 +133,7 @@ Check: `sudo -n -u specialist -H /usr/local/bin/specialist-cli arcane --caller t
133
133
 
134
134
  The `astro-discord` extension gives Discord users access to the specialists from a channel. It runs in a headless Pi host on Cortex (LaunchAgent `com.astrofoundry.astro-discord`, user `cortex`), never in interactive sessions.
135
135
 
136
- - Channels are configured one by one (`channels.<id>`): `trigger` is `always` (every message that names a specialist counts) or `mention` (only messages that start with `@Cortex` or reply to the bot); `specialists` is `*` or a list. A channel with one specialist needs no prefix: every message there is a task for it. In a channel with several, `dns list the zones` names the specialist; an addressed message without a name gets a reply asking for one. Threads inherit their parent channel's entry; unlisted channels are ignored. The bot replies at once with "<specialist> is working on your task" and shows the typing indicator until the answer replaces that reply. Reactions on your message: ⏳ running, 🔒 waiting for an owner's approval, ✅ done, ❌ failed. `help` shows the specialists you may use in that channel and the commands (`status` lists running tasks; owners in the admin channel also see the `config` commands). Each channel and specialist has one conversation: a reply to the bot's answer, or another task for that specialist within 30 minutes, continues it with everything the specialist saw before; otherwise a new one starts, and a task beginning with `new` forces that. Replied-to and forwarded messages (text, embeds, attachment names) travel with the task, so "check this" on a forwarded alert works. Image attachments on the message or on what it quotes (up to 4, 8 MiB each) are downloaded and passed to the specialist, which sees them; other file types are named only. Sessions live under `~/.config/astro-discord/sessions/<specialist>/` and are deleted after 7 days. Long answers arrive as a `.md` attachment. When the bridge restarts, open approval requests are marked cancelled and running tasks interrupted in Discord; ask again. With `dailyReport` in the config, the bridge posts an "Arcane Daily Report:" message every morning in that channel: each service with an update as current to target version, from a fresh `update-report`; replying to it asks the arcane specialist with the report as context.
136
+ - Channels are configured one by one (`channels.<id>`): `trigger` is `always` (every message that names a specialist counts) or `mention` (only messages that start with `@Cortex` or reply to the bot); `specialists` is `*` or a list. A channel with one specialist needs no prefix: every message there is a task for it. In a channel with several, `dns list the zones` names the specialist; an addressed message without a name gets a reply asking for one. Threads inherit their parent channel's entry; unlisted channels are ignored. The bot replies at once with "<specialist> is working on your task" and shows the typing indicator until the answer replaces that reply. Reactions on your message: ⏳ running, 🔒 waiting for an owner's approval, ✅ done, ❌ failed. `help` shows the specialists you may use in that channel and the commands (`status` lists running tasks; owners in the admin channel also see the `config` commands). Each channel and specialist has one conversation: a reply to the bot's answer, or another task for that specialist within 30 minutes, continues it with everything the specialist saw before; otherwise a new one starts, and a task beginning with `new` forces that. Replied-to and forwarded messages (text, embeds, attachment names) travel with the task, so "check this" on a forwarded alert works. Image attachments on the message or on what it quotes (up to 4, 8 MiB each) are downloaded and passed to the specialist, which sees them; other file types are named only. Sessions live under `~/.config/astro-discord/sessions/<specialist>/` and are deleted after 7 days. Long answers arrive as a `.md` attachment. When the bridge restarts, open approval requests are marked cancelled and running tasks interrupted in Discord; ask again. Scheduled reports are a list in the config, one entry per report: `"reports": [{ "report": "arcane", "channelId": "<id>", "time": "06:00", "timeZone": "Europe/Madrid" }, { "report": "tools", "channelId": "<admin channel id>", "time": "06:00", "timeZone": "Europe/Madrid" }]` (the earlier single `dailyReport` key is refused; move it into the list). The Arcane report posts an "Arcane Daily Report:" message every morning: each service with an update as current to target version, from a fresh `update-report`; replying to it asks the arcane specialist with the report as context. The tools report posts "Cortex Tools Report:": every tool on Cortex with a newer version (Homebrew packages installed on request and casks, Pi, pi-astro, and through `specialist-install` the wrappers, arcane-cli at the Arcane server's version, trendarr, and the specialist Node at the latest LTS), a release-notes verdict from `astro.release-reviewer` for the tools the specialists build on, an Update button per line, and one Upgrade everything for Homebrew. Only owners can press them; nothing is installed otherwise. After an update that needs it, the bridge, herdr, or Astrogate restarts as soon as nothing runs on it; after a day of waiting, a Restart now button appears. The bridge calls `sudo -n /usr/local/sbin/specialist-install` through the sudoers rule from step 4.
137
137
  - Media: where the `media` specialist is present (`config channel <#channel> always media`), `/trending [type] [min_rating] [count]`, `/search <title> [type]`, and `/downloads <status|pause|resume|unpause>` run the wrapper without a model. Lists arrive as two messages, Movies and Series, each title with a button beside it: **Add** adds it at once and turns into Added, In library, or Not found (Retry after a failure); a greyed-out In library or No IMDb ID button needs nothing. Notes such as "downloads are paused" go only to the user who clicked. Owners and `access.media` users may use them; anyone else gets a refusal only they see, and in a channel without `media` the bot names the channels that have it. Plain messages in the channel still go to the agent, for requests by name ("add the new Dune"). At startup the bridge sets the three commands as the application's global commands (servers only).
138
138
  - Access: `owners` may use every specialist and decide approvals; `access.<specialist>` lists extra users for that specialist; everyone else gets no reply. Owners change the configuration from the admin channel (`adminChannelId`) without a shell: `config show`, `config channel <#channel> <always|mention> <*|dns,edge>`, `config channel <#channel> remove`, `config access <specialist> add|remove <@user>`; the bot writes `~/.config/astro-discord/config.json` on Cortex, which is also editable by hand and re-read on every message. Deny the bot's role View Channel on channels it must never read; that is the boundary the map cannot provide.
139
139
  - Approvals: a risky call (anything with `--confirm`, plus writes such as record or zone deletes, Zitadel changes, Pomerium deploys, service restarts, and on Arcane only destructive actions: `down`, deletes, prunes, image, volume, or network removal, `system upgrade`, `gitops delete`; help lookups never ask) pauses the specialist and posts Approve/Deny buttons; only owners' clicks count; no decision within `approvalTimeoutMinutes` denies it. The specialist then reports the denial. Ambiguous tasks get a question back instead of an action.
package/themes/astro.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
2
+ "$schema": "https://pi.dev/schemas/theme.schema.json",
3
3
  "name": "astro",
4
4
  "vars": {
5
5
  "bg": "#0D1117",