@astrofoundry/pi-astro 0.23.1 → 0.23.2

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.
@@ -70,6 +70,16 @@ describe("astro-subagents extension", () => {
70
70
  expect(notify).toHaveBeenCalledWith(expect.stringContaining("depth limit"), "warning");
71
71
  });
72
72
 
73
+ it("posts the /run result without a model turn and names the model in the footer", async () => {
74
+ const pi = makePi();
75
+ astroSubagents(pi as unknown as Parameters<typeof astroSubagents>[0], { dirs, env: {} });
76
+ const setStatus = vi.fn();
77
+ const ctx = { ui: { notify: vi.fn(), setStatus }, hasUI: true, cwd: root, model: { provider: "openai-codex", id: "gpt-6-astra" } };
78
+ await pi.commands.get("run")?.handler("astro.nobody -- x", ctx);
79
+ expect(setStatus).toHaveBeenCalledWith("subagent", expect.stringContaining("astro.nobody on openai-codex/gpt-6-astra"));
80
+ expect(pi.sendMessage).toHaveBeenCalledWith(expect.objectContaining({ customType: "astro-subagents", content: expect.stringContaining("Unknown agent") }), { triggerTurn: false });
81
+ });
82
+
73
83
  it("removes legacy loader copies on session start", async () => {
74
84
  writeFileSync(join(dirs.userAgents, "astro.arcane.md"), "old copy");
75
85
  writeFileSync(join(dirs.userAgents, "mine.md"), "---\nname: mine\ndescription: keep\n---\n");
@@ -139,6 +139,12 @@ export default function astroSubagents(pi: ExtensionAPI, options: GateOptions =
139
139
  });
140
140
  }
141
141
 
142
+ /** Footer label: the agent and the model it will run on. */
143
+ function runLabel(ctx: ExtensionContext, agents: AgentConfig[], agentName: string): string {
144
+ const model = agents.find((a) => a.name === agentName)?.model ?? (ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined);
145
+ return model ? `${agentName} on ${model}` : agentName;
146
+ }
147
+
142
148
  if (canDelegate) {
143
149
  const initial = listAgents(process.cwd(), "user");
144
150
  pi.registerTool({
@@ -152,7 +158,13 @@ export default function astroSubagents(pi: ExtensionAPI, options: GateOptions =
152
158
  ],
153
159
  parameters: Params,
154
160
  async execute(_id, params, signal, onUpdate, ctx) {
155
- const label = params.chain?.length ? `chain of ${params.chain.length}` : params.tasks?.length ? `${params.tasks.length} agents` : (params.agent ?? "subagent");
161
+ const label = params.chain?.length
162
+ ? `chain of ${params.chain.length}`
163
+ : params.tasks?.length
164
+ ? `${params.tasks.length} agents`
165
+ : params.agent
166
+ ? runLabel(ctx, listAgents(ctx.cwd, params.agentScope ?? "user").agents, params.agent)
167
+ : "subagent";
156
168
  const stopTicker = startTicker(ctx, label);
157
169
  try {
158
170
  return await runTool(params, signal, onUpdate, ctx);
@@ -260,7 +272,7 @@ export default function astroSubagents(pi: ExtensionAPI, options: GateOptions =
260
272
  return;
261
273
  }
262
274
  const agents = listAgents(ctx.cwd, "user").agents;
263
- const stopTicker = startTicker(ctx, parsed.agent);
275
+ const stopTicker = startTicker(ctx, runLabel(ctx, agents, parsed.agent));
264
276
  let result: RunResult;
265
277
  try {
266
278
  result = await runOne(ctx, agents, parsed.agent, parsed.task, undefined, undefined, undefined, undefined);
@@ -269,10 +281,8 @@ export default function astroSubagents(pi: ExtensionAPI, options: GateOptions =
269
281
  }
270
282
  const status = isFailed(result) ? "failed" : "done";
271
283
  if (isFailed(result)) ctx.ui.notify(`${parsed.agent} failed`, "warning");
272
- pi.sendMessage(
273
- { customType: "astro-subagents", content: `Result from /run ${parsed.agent} (${status}). Task: ${parsed.task}\n\n${resultOutput(result)}`, display: true },
274
- { deliverAs: "followUp", triggerTurn: true },
275
- );
284
+ // No turn: the result joins the context for the next prompt without the parent model restating it.
285
+ pi.sendMessage({ customType: "astro-subagents", content: `Result from /run ${parsed.agent} (${status}). Task: ${parsed.task}\n\n${resultOutput(result)}`, display: true }, { triggerTurn: false });
276
286
  },
277
287
  });
278
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.23.1",
3
+ "version": "0.23.2",
4
4
  "description": "Personal pi customizations (extensions, subagents, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -105,7 +105,7 @@ Check: `sudo -n -u specialist -H /usr/local/bin/specialist-cli arcane --caller t
105
105
 
106
106
  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.
107
107
 
108
- - 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. Reactions on your message: ⏳ running, 🔒 waiting for an owner's approval, ✅ done, ❌ failed. `help` shows what you may use in that channel; `status` lists running tasks. Long answers arrive as a `.md` attachment.
108
+ - 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). Long answers arrive as a `.md` attachment.
109
109
  - 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.
110
110
  - Approvals: a risky call (anything with `--confirm`, plus writes such as record or zone deletes, Zitadel changes, Pomerium deploys, Arcane redeploys, service restarts) 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.
111
111
  - Setup, as `cortex`: `d=$(mktemp -d) && cp ~/.pi/agent/npm/node_modules/@astrofoundry/pi-astro/extensions/astro-discord/*.ts "$d" && node --disable-warning=ExperimentalWarning "$d/setup.ts"` (Node does not strip types inside `node_modules`, so the files are copied first) prompts for the bot token and the ids, checks each against Discord, and writes `~/.config/astro-discord/{token,config.json,run.sh}` and the LaunchAgent; it prints the `launchctl bootstrap` line. After a pi-astro update restart the bridge with `launchctl kickstart -k gui/$(id -u)/com.astrofoundry.astro-discord`. Log: `~/Library/Logs/astro-discord.log`.