カスタムエージェント、
1行のJSONで。

どんなツールでも、作業中、完了、対応待ちとしてGnistaに表示できます。Gnistaの新しいリリースは要らず、Gnista以外に入れるものもありません。何か起きるたびに、ツールが1行のJSONをGnistaのヘルパーに渡すだけです。

更新:

試してみる

Gnistaを起動した状態で、ターミナルで:

G=/Applications/Gnista.app/Contents/Helpers/gnista
echo '{"session":"demo","event":"needs-you","message":"Ralph wants to deploy","cwd":"'"$PWD"'"}' | "$G" hook custom ralph

「Ralph」のセッションが現れ、対応待ちになります。クリックすると、このターミナルに戻ります。続けて:

echo '{"session":"demo","event":"done","message":"Deployed"}' | "$G" hook custom ralph
echo '{"session":"demo","event":"end"}' | "$G" hook custom ralph

ヘルパーは必ず0で終了し、待つこともないので、ツールを止めることはありません。送った内容に問題があれば、stderrにそう書き、何も送りません。

コマンド

gnista hook custom <name> [event]

<name>はエージェントの名前です。小文字の英字、数字、-、_で32文字まで、先頭は英字か数字です。組み込みのエージェントの名前(claude、codexなど)は使えません。イベントはJSONの代わりにここで渡すこともできます。イベントごとに別のコマンドを実行するツール向けです。

JSON

1回の呼び出しにつき1つのJSONオブジェクトを、stdinに:

キー内容
session必須。1つのセッションの間ずっと変わらない任意のID(テキストか数値)。
event必須。ここかコマンドラインで。下のイベントのどれか。
cwdプロジェクトのフォルダ。行に表示されます。ないときはヘルパー自身のフォルダ。
messageエージェントがしていること、求めていること、最後に言ったこと。
tool使っているツール。toolとtool-doneで。
titleセッションのタイトル。フォルダの代わりに表示されます。
modelモデル。例:gpt-5。Gnistaが知っているモデルは、アイコンに小さなバッジで表示されます。それ以外は独自に記述できます。
transcriptエージェントのトランスクリプトファイル。detailsで使います(バージョン2)。Claude形式のフックはtranscript_pathとして送ります。
pidエージェントのプロセス。終了するとセッションも終わります。ないときはヘルパーを実行したプロセス。

イベント

大文字小文字や区切り文字は区別しません。needs-you、NeedsYou、needs_youはどれも同じです。

イベントGnistaでは
startセッションが現れ、作業中になります。
prompt作業中:誰かが何かを頼みました。
tool作業中。「コマンドを実行中」「編集中:api.ts」かmessageを表示します。
tool-done作業中のまま。ただしツールがdoneAfterToolsのどれかなら完了。
needs-you対応待ち:ノッチがmessageとともに知らせます。
doneターンが完了しました。
endセッションが終わりました。

作業中のセッションが静かになると、数分後にアイドルになります(設定 ▸ 一般 ▸ アイドルまでの時間)。

エージェントファイル

任意です。~/Library/Application Support/Gnista/Agents/<name>.jsonに置きます。どのキーも任意です:

{
  "name": "Ralph",
  "icon": "ralph.svg",
  "fields": {
    "session": "$.conversation_id",
    "event": "$.hook_event_name",
    "cwd": "$.workspace_roots[0]",
    "tool": "$.tool.name"
  },
  "events": {
    "beforeSubmitPrompt": "prompt",
    "afterToolUse": "tool-done",
    "stop": "done",
    "heartbeat": "ignore"
  },
  "host": "com.example.Ralph",
  "jump": { "url": "ralph://open?session={session}" },
  "doneAfterTools": ["attempt_completion"]
}
キー役割
name行と設定に表示されるエージェントの名前。40文字まで。ないときはコマンドの名前。
icon同じフォルダにあるSVGかPNGのファイル。1MBまで。ないときはGnista自身の火花。
fields独自のJSONを送るツール向け:上のJSONの表の各値をどこから読むか。パスは$から始まり、オブジェクトのキーは.key、リストの項目は[0]。指定しないフィールドは、いつものキーから読みます。
eventsツール独自のイベント名と、それぞれがGnistaのどのイベントにあたるか。"ignore"で静かに捨てられます。名前は同じゆるさで照合され、Gnista自身の名前もそのまま使えます。
hostエージェントが動くアプリのバンドルID。Gnistaがプロセスから判断できないとき、クリックでここへジャンプします。
jumpジャンプのルール。下を参照。
doneAfterToolsターンの完了を伝えないエージェント向け:これらのツールのどれかのあと(tool-doneイベント)で完了とします。

1MBを超えるファイルや、どこかに誤りがあるファイル(知らないキー、パスになっていないパス、Gnistaにないイベント)は、まるごと無視されます。設定 ▸ エージェント ▸ カスタムエージェントに理由とともに表示され、ヘルパーもstderrに書きます。その間も、上のシンプルな形式はそのまま使えます。

Gnistaに接続を任せる

"version": 2にすると、ファイルでエージェントの接続のしかたもGnistaに伝えられます。組み込みのエージェントと同じように、エージェント自身の設定に入れるフック、フックが送る内容の読み方、許可リクエストをノッチから答えられるか、トランスクリプトの場所(行にプロンプトと返信とコストが表示されます)です。バージョン1のキーの意味は変わりません。

{
  "version": 2,
  "name": "Ralph",
  "colour": "#2EB872",
  "adapter": "claude-style",
  "hooks": {
    "format": "claude-table",
    "file": ".ralph/settings.json",
    "events": ["UserPromptSubmit", "PreToolUse", "PostToolUse", "PermissionRequest", "Stop", "SessionEnd"],
    "timeout": 5
  },
  "approvals": "plain",
  "details": "claude"
}
キー内容
version2。
speaker文中での呼び方:「Ralphが…を実行したがっています」。40文字まで。ないときはname。
colour色。"#RRGGBB"の形で。
adapter"fields"(既定):fieldsとeventsで、バージョン1と同じようにツールのJSONからGnistaの値を探します。"claude-style":ツールがClaude Codeのフックのペイロードを送ります。その場合eventsは、ツール独自のイベント名をClaudeのものに対応づけます。
toolNamesclaude-styleのとき:Claudeのツールに対するツール独自の名前。例:{"run_shell": "Bash"}。行とリクエストの表示がそろいます。
hooks接続したときにGnistaが入れるもの:下の表。
approvals許可リクエストをノッチで待ちます:"plain"(許可か拒否)、"answers"(質問への答え付き)、"claude"(答え、「常に」ルール、モード付き)。claude-styleで、PermissionRequestにclaude-tableのフックがあるときだけ。
detailsGnistaが読めるトランスクリプトを書き出すなら、その形式:"claude"、"codex"、"gemini"、"antigravity"。行にはプロンプトと返信が表示され、antigravity以外ではコストも表示されます。

設定 ▸ エージェントには、あなたのエージェントが他のエージェントと並びます。そこで接続すると、指定したファイルにGnistaのフックが加わり、接続を外すと取り除かれます。Gnistaが変えるのは自分のエントリだけで、先にファイルをバックアップします。フックはイベントを付けてgnista hook custom <name>を呼びます。

「hooks」の中内容
format"claude-table":Claude Codeの{"hooks": {"<event>": [{"hooks": [{"type": "command", …}]}]}}。イベントはペイロードに入ります。"flat":{"hooks": {"<event>": [{"command": …}]}}。イベントはコマンドラインに入ります。"toml":eventとcommandを持つ[[hooks]]ブロック。ファイル名は.tomlで終わること。
fileエージェントの設定ファイル。ホームフォルダからの相対パスで、エージェント自身のフォルダの中:.ralph/settings.json。そのフォルダがあれば、このMacにエージェントがあるとみなします。
eventsGnistaがフックするエージェントのイベント名:英字、数字、_。
timeout秒数(1〜600)。フックにタイムアウトを持つエージェント向け。
foundByエージェント自身だけが書くファイル(ホームフォルダからの相対パス)。フォルダだけではインストール済みか分からないとき(他のアプリも作るため)に使います。

ジャンプのルール

セッションをクリックすると、Gnistaはそのターミナルのタブかウインドウへ連れて行きます。中に入る手段がないアプリでは、ジャンプのルールが代わりに開くURLを渡します。正確なタブとウインドウを試したあと、アプリを手前に出すだけの前に使われます。

{ "url": "myeditor://focus?folder={cwd}&pane={tmux_pane}" }

エージェントファイルの"jump"として使うか、あるアプリのすべてのセッションに向けて~/Library/Application Support/Gnista/JumpRules/<bundle id>.json(例:com.example.Editor.json)に置きます。エージェント自身のルールが優先です。

プレースホルダー入る値
{session}セッションID。
{cwd}プロジェクトのフォルダ。
{tty}ターミナル。例:/dev/ttys004。
{pid}エージェントのプロセスID。
{tmux_pane}tmuxのペイン。例:%3。

どの値もまるごとパーセントエンコードされる(/は%2Fになる)ので、URLの形は変えられません。プレースホルダーのどれかに値がないルールは使われません。ファイルを開いたりコードを実行したりするURL(file:、javascript:、data:など)は拒否されます。

設定で

設定 ▸ エージェント ▸ カスタムエージェントには、ファイルがあるか報告してきたエージェントがすべて並び、Gnistaが最後に連絡を受けた時刻も表示されます。