Pi Agent Registrierung von Befehlen und Tastenkürzeln

Registrieren Sie benutzerdefinierte Befehle, Tastenkürzel und CLI-Flags über Erweiterungen, damit sich Pi Agent besser an Ihren Workflow anpasst.


Benutzerdefinierte Befehle registrieren

Verwendungpi.registerCommand()Registrieren Sie Befehle, die Benutzer mit / aufrufen können.

Die folgende Erweiterung demonstriert sowohl einen einfachen Befehl als auch einen Befehl mit Parameter-Autovervollständigung:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/stats-command.ts

// Einfacher Befehl
pi.registerCommand("stats", {
  description: "Statistiken für die aktuelle Sitzung anzeigen", // Erforderlich, wird in der / Autovervollständigungsliste angezeigt
  handler: async (args, ctx) => {
    const count = ctx.sessionManager.getEntries().length;
    ctx.ui.notify(`Insgesamt ${count}Nachrichten`, "info");
  }
});

// Befehl mit Parameter-Autovervollständigung
pi.registerCommand("deploy", {
  description: "In der angegebenen Umgebung bereitstellen",
  // Parameter-Autovervollständigung definieren; prefix ist das Parameterpräfix, das der Benutzer bereits eingegeben hat
  getArgumentCompletions: (prefix) => {
    const envs = ["dev", "staging", "prod"];
    const items = envs
      .filter(e => e.startsWith(prefix))
      .map(e => ({ value: e, label: e }));
    return items.length > 0 ? items : null; // null zurückgeben, wenn es keine Übereinstimmung gibt
  },
  handler: async (args, ctx) => {
    if (!args) {
      ctx.ui.notify("Verwendung: /deploy <Umgebungsname>", "warning");
      return;
    }
    ctx.ui.notify(`Bereitstellung in der ${args}-Umgebung...`, "info");
  },
});

Nach dem Speichern führe /reload aus, um die Erweiterung zu laden, und gib dann /stats im Editorbereich ein, um den Effekt zu sehen:

/stats
共 42 条消息

Wenn mehrere Erweiterungen Befehle mit demselben Namen registrieren, behält Pi Agent alle Registrierungen bei und hängt numerische Suffixe in der Ladereihenfolge an, z. B. /review:1 und /review:2.

Dies vermeidet Namenskonflikte zwischen Erweiterungen.


Erweiterungen, die proaktiv Nachrichten senden

Neben der passiven Reaktion auf Befehle und Ereignisse können Erweiterungen auch proaktiv den Gesprächsverlauf steuern.

pi.sendMessage(message, deliverAs?)Wird verwendet, um benutzerdefinierte Nachrichten einzufügen,pi.sendUserMessage(text, deliverAs?)simuliert eine Benutzereingabe.

deliverAs hat drei Werte: steer ist der Standard, der die Nachricht in den aktuellen Turn einfügt; followUp hängt sie nach diesem Turn an; nextTurn startet einen neuen Turn (wird nur von sendMessage unterstützt).

sendUserMessage löst immer eine Antwort aus, denn für die KI ist es eine echte Benutzeräußerung.

Wenn diese beiden Methoden während der Streaming-Ausgabe aufgerufen werden, muss deliverAs explizit angegeben werden, sonst wird sofort ein Fehler geworfen.

Beispiel

// Dateipfad: ~/.pi/agent/extensions/follow-up.ts
// Der folgende Ausschnitt befindet sich im Funktionskörper der Erweiterungs-Factory

// Simuliere eine ergänzende Anfrage eines Benutzers: nachdem alle aktuellen Tools die Ausführung beendet haben, anhängen
pi.sendUserMessage("Nach der Bereitstellung bitte auch die Logs der Produktionsumgebung prüfen", {
  deliverAs: "followUp",
});

// Nur Referenzinformationen in den Kontext injizieren, ohne eine Benutzeräußerung zu simulieren
pi.sendMessage({
  customType: "deploy-context",
  content: "Das aktuelle Deployment-Ziel ist Produktion",
  display: true,
}, {
  deliverAs: "steer", // Standardwert, in den aktuellen Turn eingefügt
});

sendMessage unterstützt auch triggerTurn: true, wodurch sofort eine LLM-Antwort ausgelöst wird, wenn der Agent im Leerlauf ist.

Die Wahl zwischen den beiden ist einfach: Verwende sendUserMessage, wenn die KI tatsächlich antworten soll, und sendMessage, wenn du nur Referenzinformationen bereitstellen möchtest.


Befehlskontext (ExtensionCommandContext)

Der Command-Handler erhält einenExtensionCommandContextExtensionCommandContext, der von ExtensionContext erbt.

Zusätzlich zum normalen Kontext bietet er die folgenden Methoden:

MethodeBeschreibung
ctx.getSystemPromptOptions()Liest die Basiseingabe zum Erstellen des System-Prompts (contextFiles, skills, etc.), nur im Befehlskontext verfügbar.
ctx.waitForIdle()Warte, bis die KI vollständig inaktiv ist (einschließlich Wiederholungen, Kompaktierung und abgeschlossener Folge-Nachrichten).
ctx.newSession(options)Erstelle eine neue Sitzung, mit der Möglichkeit, eine übergeordnete Sitzung und Initialisierungslogik anzugeben.
ctx.fork(entryId, options)Zweige eine neue Sitzung von einem angegebenen Eintrag ab.
ctx.navigateTree(targetId, options)Navigiere zu einer angegebenen Position im Sitzungsbaum.
ctx.switchSession(sessionPath, options)Wechsle zu einer anderen Sitzungsdatei.
ctx.reload()Führe denselben Hot-Reload-Prozess wie /reload aus.

Der folgende Befehl listet alle Sitzungen des aktuellen Projekts als Auswahlliste auf; sobald eine ausgewählt wurde, wird direkt zu ihr gewechselt:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/switch-session.ts
import { SessionManager } from "@earendil-works/pi-coding-agent";

pi.registerCommand("switch", {
  description: "Zu einer anderen Sitzung wechseln",
  handler: async (args, ctx) => {
    // Alle Sitzungen des aktuellen Projekts auflisten
    const sessions = await SessionManager.list(ctx.cwd);
    if (sessions.length === 0) {
      ctx.ui.notify("Keine Sitzungen verfügbar", "warning");
      return;
    }

    // Eine Auswahlliste einblenden
    const choice = await ctx.ui.select(
      "Wählen Sie die Sitzung aus, zu der gewechselt werden soll:",
      sessions.map(s => s.file),
    );

    if (choice) {
      // Zur ausgewählten Sitzung wechseln
      await ctx.switchSession(choice, {
        withSession: async (ctx) => {
          ctx.ui.notify("Sitzung gewechselt", "info");
        },
      });
    }
  },
});
/switch
┌ 选择要切换的会话:
│ > ~/.pi/agent/sessions/my-project/abc123.jsonl
│   ~/.pi/agent/sessions/my-project/def456.jsonl
└
已切换会话

Hinweise zur Sitzungsersetzung: Der ctx im withSession-Callback ist ein brandneuer Kontext.

Verwenden Sie nicht das alte pi-Objekt oder den in Command-Callbacks erfassten ctx; sie werden nach der Sitzungsersetzung ungültig.


Tastaturkürzel registrieren

Verwendungpi.registerShortcut()Benutzerdefinierte Tastaturkürzel registrieren.

Das Folgende bindet "Toggle Plan Mode" an eine Tastenkombination, die noch von keiner integrierten Belegung verwendet wird:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/plan-shortcut.ts

pi.registerShortcut("ctrl+shift+g", {
  description: "Planmodus umschalten",
  handler: async (ctx) => {
    ctx.ui.notify("Planmodus umgeschaltet!", "info");
  },
});

Nach dem Start die Tastenkombination drücken, um sie auszulösen:

已切换 Plan 模式!

Bevor Sie eine Tastenkombination wählen, prüfen Sie die Standard-Tastenkürzel-Tabelle, um das Überschreiben integrierter Bindungen zu vermeiden.

Zum Beispiel ist ctrl+shift+p bereits durch "Zum vorherigen Modell wechseln" belegt; wenn dieselbe Tastenkombination erneut registriert wird, geraten die beiden Verhaltensweisen in Konflikt.

Das Tastenkürzel-Format istModifikatortaste + Taste, z. B. ctrl+shift+g, alt+x usw.


CLI-Flags registrieren

Verwendungpi.registerFlag()Fügen Sie benutzerdefinierte CLI-Argumente zum pi-Befehl hinzu.

Das folgende Beispiel registriert ein boolesches --plan-Flag, damit die KI zuerst einen Plan erstellt, bevor sie handelt:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/plan-flag.ts

pi.registerFlag("plan", {
  description: "Im Plan-Modus starten",
  type: "boolean",
  default: false,
});

// Prüfe den Flag-Wert im Erweiterungscode
// Hinweis: pi.getFlag() hat erst nach abgeschlossener Verarbeitung der Kommandozeilenargumente einen Wert,
// Daher ist es empfehlenswert, die Prüfung in einen Event-Callback zu legen statt auf die oberste Ebene des Erweiterungsmoduls.
// Die Auswertung des Moduls auf oberster Ebene erfolgt während des Ladens der Erweiterung; zu diesem Zeitpunkt liest sie immer den Standardwert false.
pi.on("before_agent_start", async (event, ctx) => {
  if (!pi.getFlag("plan")) return; // Wenn --plan nicht aktiviert ist, keine Änderungen vornehmen
  return {
    systemPrompt: event.systemPrompt
      + "\n\nPlanmodus: Zuerst einen Plan vorschlagen, dann nach Bestätigung durch den Benutzer ausführen.",
  };
});

Verwendung:

$ pi --plan "帮我实现用户认证功能"

Kommunikation zwischen Erweiterungen

Verwendungpi.eventsEreignisse zwischen Erweiterungen teilen.

Der Absender muss nicht wissen, wer zuhört, und der Listener muss nicht wissen, woher das Ereignis kommt:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/deploy-events.ts

// Erweiterung A: Ereignisse senden
pi.events.emit("deploy:completed", {
  env: "production",
  timestamp: Date.now(),
});

// Erweiterung B: auf Ereignisse lauschen (normalerweise in einer anderen Erweiterungsdatei geschrieben)
pi.events.on("deploy:completed", (data) => {
  console.log(`Bereitstellung abgeschlossen: ${data.env}`);
});

Registrierte Befehle abrufen

Verwendungpi.getCommands()Alle verfügbaren Befehle in der aktuellen Sitzung abrufen.

Das folgende Beispiel registriert einen /my-commands-Befehl, um alle von Erweiterungen registrierten Befehle aufzulisten:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/list-commands.ts

pi.registerCommand("my-commands", {
  description: "Alle von Erweiterungen registrierten Befehle auflisten",
  handler: async (args, ctx) => {
    const commands = pi.getCommands();

    // Nach Quelle kategorisieren
    const extCommands = commands.filter(
      c => c.source === "extension"
    );
    const templates = commands.filter(
      c => c.source === "prompt"
    );
    const skills = commands.filter(
      c => c.source === "skill"
    );

    // Das Ergebnis anzeigen, damit nicht nur berechnet wird, ohne etwas auszugeben
    ctx.ui.notify(
`Erweiterungsbefehle ${extCommands.length}:`
        + extCommands.map(c => "/" + c.name).join("、"),
      "info"
    );
    console.table(commands);
  },
});
/my-commands
扩展命令 2 个:/stats、/deploy

Jeder Befehlseintrag enthält die folgenden Felder:

FeldTypeBeschreibung
namestringBefehlsname, aufgerufen als /name im Editorbereich.
descriptionstringBefehlsbeschreibung, angezeigt in der Autovervollständigungsliste
sourcestringQuellentyp: extension, prompt, skill, usw.
sourceInfoObjektDetaillierte Quellmetadaten, z. B. der Erweiterungspfad, über den es registriert wurde

Dynamische Tool-Verwaltung

Tools zur Laufzeit aktivieren und deaktivieren.

Ein typischer Anwendungsfall besteht darin, Tools für Schreiboperationen vorübergehend zu deaktivieren, sodass die KI während einer einzelnen Aufgabe nur lesen, aber nicht ändern kann:

Beispiel

// Dateipfad: ~/.pi/agent/extensions/manage-tools.ts

// Liste der aktuell aktivierten Tools abrufen
const active = pi.getActiveTools();
// Zum Beispiel: ["read", "bash", "edit", "write"]

// Metadaten für alle registrierten Tools abrufen
const all = pi.getAllTools();

// Integrierte Tools filtern
const builtinTools = all.filter(
  t => t.sourceInfo.source === "builtin"
);

// Von Erweiterungen registrierte Tools filtern
const extTools = all.filter(
  t => t.sourceInfo.source !== "builtin"
    && t.sourceInfo.source !== "sdk"
);

// Benutzerdefinierte Tools dynamisch aktivieren (vorhandene Tools beibehalten)
pi.setActiveTools([...new Set([...active, "my_tool"])]);

// In den schreibgeschützten Modus wechseln: nur Tools vom Lesetyp behalten,
// mit Ausnahme von bash / edit / write; in diesem Zustand kann die KI keine Befehle ausführen oder Dateien ändern
pi.setActiveTools(["read", "grep", "find", "ls"]);

Hier wirken setActiveTools und die Kommandozeilen--tools / --exclude-tools⟧parameter auf denselben Toolsatz.parameter act on the same tool set.

Der CLI-Parameter bestimmt das anfängliche Set beim Start, während setActiveTools es zur Laufzeit dynamisch anpasst; beide können zusammen verwendet werden.

An setActiveTools übergebene Änderungen müssen additiv sein; das Entfernen aktuell aktivierter Tools im selben Aufruf führt zum Verlust der Lazy-Loading-Optimierung.

Darüber hinaus wird durch das Aktivieren von Tools mit promptSnippet oder promptGuidelines der System-Prompt neu erstellt; selbst wenn das Modell Lazy Loading unterstützt, kann dadurch der zwischengespeicherte Prompt-Präfix ungültig werden.

Weitere Erweiterungen