Custom Agents: integration guide (Beta)
This is the complete guide to showing an AI agent you built in the Vibe Island notch. It is written so a coding agent can follow it step by step. The integration is one shell command per event: no SDK, no hooks system needed, and the command never fails your agent (it exits 0 whether or not Vibe Island is running and does not wait for a reply). Beta: needs Vibe Island 1.0.53 or newer, direct download edition.
How to run this guide
Section titled “How to run this guide”Do the work yourself, in the user’s project: edit the agent’s own code or hook configuration (Vibe Island never edits it for you), and never just hand the user a list of commands. Stop and ask the user only at the points marked “Ask the user” or “Stop and tell the user”; otherwise keep going. Never edit anything under ~/.vibe-island/ except the one optional manifest file in Step 5. Every step ends with “Check passed when …”; do not move on before it holds. When you are done, print the report from Step 7.
Try it by hand
Section titled “Try it by hand”Pick a name for your agent (lowercase letters, digits, - or _; the examples use acme-coder), make sure Vibe Island is running, and run these one at a time. Move the pointer to the notch to open the panel.
echo '{"hook_event_name":"SessionStart","session_id":"demo-1","cwd":"/tmp"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coderA card for tmp appears, carrying your agent’s name (as a text tag, or as an A badge you can hover).
echo '{"hook_event_name":"Stop","session_id":"demo-1","cwd":"/tmp","last_assistant_message":"Done."}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coderThe card shows the completion notice Done.
echo '{"hook_event_name":"SessionEnd","session_id":"demo-1","cwd":"/tmp"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coderThe card goes away. Your agent sends the same three events when a session opens, a turn ends and the session closes.
Procedure
Section titled “Procedure”Step 1: Check the preconditions
Section titled “Step 1: Check the preconditions”# Edition: a Mac App Store install carries a receipt.[ -e "/Applications/Vibe Island.app/Contents/_MASReceipt/receipt" ] && echo "edition: Mac App Store" || echo "edition: direct download"
# The command exists and is executable.BRIDGE="$HOME/.vibe-island/bin/vibe-island-bridge"[ -x "$BRIDGE" ] && echo "command: ok" || echo "command: MISSING"
# Vibe Island is running.pgrep -x vibe-island >/dev/null && echo "app: running" || echo "app: NOT RUNNING"
# Installed version (works when the app is in /Applications).echo "version: $(defaults read "/Applications/Vibe Island.app/Contents/Info" CFBundleShortVersionString 2>/dev/null || echo unknown)"| Result | Stop and tell the user |
|---|---|
edition: Mac App Store | Custom agents are not supported in the Mac App Store edition yet. |
command: MISSING | Vibe Island is not installed or was never opened. Install the latest version from vibeisland.app and open it once. After Remove All Auto-Configuration (Settings > About), turn an integration back on in Settings > Integrations. |
app: NOT RUNNING | Open Vibe Island. Events sent while it is closed are dropped. |
version: below 1.0.53 | Update Vibe Island to 1.0.53 or newer. If it says unknown, ask which version is installed. |
The edition and version checks assume the app is at /Applications/Vibe Island.app. If it is installed elsewhere, ask the user for the version and edition instead of guessing.
Check passed when the edition is the direct download, the command is executable, the app is running, and the version is 1.0.53 or newer.
Step 2: Choose the source name
Section titled “Step 2: Choose the source name”The source name is your agent’s identity: the value of --source, the file name of the optional manifest, and the prefix of every session id. Pick it from the agent’s product name (or keep the user’s own name for it) and keep it for good.
- It matches
[a-z][a-z0-9_-]{0,63}: lowercase letters, digits,_and-, starting with a letter. - It is not a built-in agent’s name. The check below lists them; the list grows, so put your product or company in the name.
- It has no
_custom_in it: a built-in name plus_custom_, such asclaude_custom_0, is reserved. - Prefer 12 characters or fewer: a longer name can send a click to the wrong tab in Ghostty, see Jumping back to your app.
Good: acme-coder, lint_bot. Bad: claude (handled as that built-in agent), Acme Coder (uppercase, a space; use the manifest’s display name), 2fast (starts with a digit).
SOURCE="acme-coder"printf '%s' "$SOURCE" | grep -Eq '^[a-z][a-z0-9_-]{0,63}$' && echo "format: ok" || echo "format: INVALID"case "$SOURCE" in claude|codex|zcode|gemini|antigravity|cursor|trae|opencode|mimocode|devin|droid|qoder|qodercn|qoderwork|qwenwork|qwen|grok|copilot|vscodeagent|codebuddy|workbuddy|kiro|kimi|kimicode|deepseek|dsh|mistralvibe|gajaecode|hermes|amp|pi|ohmypi|openclaw|*_custom_*) echo "name: RESERVED" ;; *) echo "name: ok" ;;esacIf the product is a fork of a built-in agent that only moved its config folder and kept its hook protocol, this guide is the wrong tool: add the folder in Settings > Integrations > CLI Hooks instead.
Check passed when both lines say ok.
Step 3: Decide where to hook in
Section titled “Step 3: Decide where to hook in”Your agent does not need a hooks system: run the command from wherever your code knows the event happened, whether that is a hook, a callback, an event handler, or a wrapper script around your agent. Map the moments in its lifecycle to events. The smallest set that works is SessionStart plus Stop.
| Moment in your agent | Event | What the user sees |
|---|---|---|
| A session opens | SessionStart | The card appears, idle. Minimum set. |
| The user sends a message | UserPromptSubmit | Working; the prompt is the summary. |
| A tool call starts or ends | PreToolUse, PostToolUse | Working, with the tool name. |
| The agent needs an approval | Notification with "notification_type":"permission_prompt" | A read-only reminder, see Approval reminders. |
| The turn finishes | Stop | Idle, and a completion notice with the reply if you send last_assistant_message. Minimum set. |
| The turn fails | StopFailure | Idle. |
| The session closes | SessionEnd | The card is removed. |
Call the command from the agent’s own process, in its own terminal environment: from a detached background service it still reports status, but a click on the card cannot return to the agent’s terminal. The agent must run as a local process; a packaged desktop app counts, a page in a browser tab does not.
Check passed when you can name the code location for SessionStart, Stop and SessionEnd.
Step 4: Send events
Section titled “Step 4: Send events”Replace acme-coder with your source name and demo-1 with your session id. Put cwd in every event: after Vibe Island restarts, the next event of a running session brings its card back, and cwd names it.
# Session openedecho '{"hook_event_name":"SessionStart","session_id":"demo-1","cwd":"/tmp"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# User sent a messageecho '{"hook_event_name":"UserPromptSubmit","session_id":"demo-1","cwd":"/tmp","prompt":"Fix the failing login test"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Tool call startedecho '{"hook_event_name":"PreToolUse","session_id":"demo-1","cwd":"/tmp","tool_name":"Bash","tool_input":{"command":"npm test"}}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Tool call endedecho '{"hook_event_name":"PostToolUse","session_id":"demo-1","cwd":"/tmp","tool_name":"Bash"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Turn finishedecho '{"hook_event_name":"Stop","session_id":"demo-1","cwd":"/tmp","last_assistant_message":"Fixed the login test."}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Turn failedecho '{"hook_event_name":"StopFailure","session_id":"demo-1","cwd":"/tmp","last_assistant_message":"The model API returned an error."}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Needs approval (read-only reminder)echo '{"hook_event_name":"Notification","session_id":"demo-1","cwd":"/tmp","notification_type":"permission_prompt","tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coder
# Session closedecho '{"hook_event_name":"SessionEnd","session_id":"demo-1","cwd":"/tmp"}' | ~/.vibe-island/bin/vibe-island-bridge --source acme-coderIn the agent’s code, send events through one helper that builds the JSON with a serializer, sends each session’s events in order, and gives the command a short timeout. This Node.js helper does that; port it to the project’s language.
// Node.js 18 or newer. Events are sent one at a time, in order, and never throw.import { spawn } from "node:child_process";import os from "node:os";import path from "node:path";
const BRIDGE = path.join(os.homedir(), ".vibe-island", "bin", "vibe-island-bridge");const SOURCE = "acme-coder";let queue = Promise.resolve();
function send(event) { return new Promise((resolve) => { try { const child = spawn(BRIDGE, ["--source", SOURCE], { stdio: ["pipe", "ignore", "ignore"] }); const timer = setTimeout(() => child.kill(), 2000); const done = () => { clearTimeout(timer); resolve(); }; child.on("error", done); child.on("exit", done); child.stdin.on("error", () => {}); child.stdin.end(JSON.stringify(event)); } catch { resolve(); } });}
export function emit(event) { queue = queue.then(() => send(event));}
emit({ hook_event_name: "SessionStart", session_id: "demo-1", cwd: process.cwd() });Check passed when every event the agent can produce goes through the helper and a failure in it cannot stop the agent.
Step 5: Add the manifest file (optional)
Section titled “Step 5: Add the manifest file (optional)”Skip this step unless you need a display name, a jump rule, custom event or field names, a custom stdout, or automatic turn end for a bot. Standard events need no file.
Path: ~/.vibe-island/integrations/<source>.json. The command re-reads it on every call, and Vibe Island picks up changes within a few seconds, without restarting anything. The smallest useful file is {"displayName": "Acme Coder"}.
| Key | Type | Effect | Default |
|---|---|---|---|
displayName | string | The name on the card: a text tag, or a first-letter badge with the full name on hover, per the user’s display setting. | The source name, each word capitalized (Acme-Coder). |
stdoutResponse | string | Printed to stdout on every call; an empty string prints an empty line. | {} |
fieldMap | object of strings | Renames top-level payload fields, {"yourName": "standard_name"}. Applied first. | none |
eventMap | object of strings | Translates event names, {"your_event": "Stop"}. Exact match, case included. Applied after fieldMap. | none |
terminatingTools | array | Ends the turn when a tool call finishes, see below. | none |
bundleIdentifier, jumpRule | string, object | Your app’s bundle identifier and {"method":"urlScheme","template":"..."}, to land on the right conversation: Jumping back to your app. | none |
A key with the wrong type is ignored. A file that is not valid JSON, not an object, or larger than 1 MiB is ignored as a whole; the command then works with the defaults.
A manifest for an agent whose payload uses different names:
{ "displayName": "Acme Coder", "stdoutResponse": "", "fieldMap": { "event": "hook_event_name", "conversation_id": "session_id", "working_directory": "cwd" }, "eventMap": { "task_completed": "Stop", "tool_call": "PreToolUse" }, "terminatingTools": [ "WeComReply", { "name": "SlackPostMessage", "when": { "is_final": true } } ]}With that file, {"event":"task_completed","conversation_id":"c-1","working_directory":"/tmp"} is delivered as a Stop event for session c-1 in /tmp.
fieldMapandeventMapsee top-level keys only. If both the old and the new field are present, the old one wins.- Standard spellings need no map. An event without
hook_event_nameafter mapping is skipped. terminatingToolsis for agents that never sendStop, such as a bot that calls a “send reply” tool and then waits. After a matchingPostToolUsethe command adds aStop. A rule matches whentool_nameequalsnameand every field inwhen(if present) equals the same top-level field oftool_input, compared by JSON type (trueand1differ).
Check passed when the file parses (python3 -m json.tool < ~/.vibe-island/integrations/acme-coder.json) and is named exactly <source>.json.
Step 6: Verify without seeing the notch
Section titled “Step 6: Verify without seeing the notch”You cannot see the notch: verify through the command and the log, then ask the user.
The command prints {} (or your stdoutResponse) and exits 0 whether or not Vibe Island is running, so exit 0 and {} do not prove delivery; if the app cannot be found at all it prints nothing (Step 1 catches that). Each call also logs one JSON line to ~/Library/Logs/VibeIsland/bridge.log with the keys t (UTC time), s (source name) and m (message). Key order varies, so match substrings.
Ask the user to watch the notch (moving the pointer to it opens the panel), then run this script. It sends a complete demo session, waits so the user can watch the card, closes it, and prints the log lines for your source.
#!/bin/shSOURCE="acme-coder" # your source nameBRIDGE="$HOME/.vibe-island/bin/vibe-island-bridge"LOG="$HOME/Library/Logs/VibeIsland/bridge.log"SID="verify-$(date +%s)" # a new id for every runSTART=$(date -u +%Y-%m-%dT%H:%M:%SZ)
send() { out=$(printf '%s' "$1" | "$BRIDGE" --source "$SOURCE") echo "exit=$? stdout=$out"}entries() { grep "\"s\":\"$SOURCE\"" "$LOG" | awk -v start="$START" ' match($0, /"t":"[^"]*"/) { t = substr($0, RSTART + 5, RLENGTH - 6); if (t >= start) print }'}
echo "1/6 SessionStart"; send '{"hook_event_name":"SessionStart","session_id":"'"$SID"'","cwd":"/tmp"}'sleep 3echo "2/6 UserPromptSubmit"; send '{"hook_event_name":"UserPromptSubmit","session_id":"'"$SID"'","prompt":"Vibe Island integration check"}'echo "3/6 PreToolUse"; send '{"hook_event_name":"PreToolUse","session_id":"'"$SID"'","tool_name":"Bash","tool_input":{"command":"echo check"}}'sleep 3echo "4/6 PostToolUse"; send '{"hook_event_name":"PostToolUse","session_id":"'"$SID"'","tool_name":"Bash"}'echo "5/6 Stop"; send '{"hook_event_name":"Stop","session_id":"'"$SID"'","last_assistant_message":"Integration check finished."}'sleep 8echo "6/6 SessionEnd"; send '{"hook_event_name":"SessionEnd","session_id":"'"$SID"'"}'sleep 1
PROBLEMS='connect failed|send failed|invalid JSON|missing hook_event_name|drop admission'echo "--- log lines for $SOURCE since $START"entries | grep -E "\"m\":\"event=|$PROBLEMS"echo "--- events logged: $(entries | grep -c '"m":"event=') (expected 6)"echo "--- problems: $(entries | grep -c -E "$PROBLEMS") (expected 0)"| Log line contains | Meaning | What to do |
|---|---|---|
"m":"event=Stop" | The event was recognized. Expect one line per call. | Nothing. |
connect failed or send failed | Vibe Island was not running or not reachable; the event was dropped. A successful delivery writes no line in release builds, so no such line after an event= line is the signal. | Ask the user to open Vibe Island, then run the script again. |
invalid JSON or missing hook_event_name | stdin was not a JSON object, or had no event name after mapping; the event was skipped. | Fix how the JSON is built, or add hook_event_name (or a fieldMap entry for it). |
drop admission hook | The user blocked the app that launched the command. | Ask the user to remove it in Settings > Notifications > Blocked Launcher Apps. |
Ask the user to confirm, in this order:
- A card for
tmpwith anAbadge or anAcme Codertag (or your display name) appeared, idle. - Three seconds later it turned to working, with the
Bashtool and the summaryVibe Island integration check. - Three seconds after that it went idle with the completion notice
Integration check finished. - Clicking the card (between the notice and the end of the script) brought the terminal window or app the agent runs in to the front.
- Eight seconds after the notice the card disappeared.
No card although the log is clean: compare the log’s "s" value with your source name, and check for a user rule hiding the card. A click in the wrong place: note which terminal or app the agent runs in and where it landed.
Check passed when the script prints six event= lines, zero problems and exit=0 for every call (the events reached Vibe Island; no log line says what the notch displayed), and the user confirmed the card.
Critical rules
Section titled “Critical rules”Stopends a turn, not the session. SendSessionEndwhen the session closes, on every exit path you control; otherwise the card lingers until idle cleanup.- Use one unique id per session, the same on every event of that session, and make it a random UUID: ids that start with the same characters (
chat-1,chat-2) can send a click to the wrong tab, see Jumping back to your app. Never reuse an id for a new conversation, and send nothing for an id after itsSessionEnd. - Never block your agent on the command: 2-second timeout, ignore the result, send each session’s events in order.
- Ignore stdout; do not require
{}. - Build the JSON with a serializer, never by joining strings.
- Use your own source name, and send
Notificationonly withpermission_prompt; any otherNotificationreturns the card to idle.
Step 7: Report back
Section titled “Step 7: Report back”Print this block for the user, filled in:
Vibe Island integration reportSource name: acme-coderFiles changed: <paths>Events wired: <event: file and function, one per line>Manifest: <written at ~/.vibe-island/integrations/acme-coder.json | not needed>Verification: <n>/6 events logged, <n> problemsTo confirm: <what the user still has to check by eye: card appeared, went idle, click landed on the right window>Skipped: <anything not done, and why>If something did not work, a requirement is not covered, or you needed a workaround, ask the user whether you should draft an email to [email protected]. You write the draft and show it; the user reviews and sends it. Never send it yourself.
- It contains: what the agent is and does; the Vibe Island and macOS versions (
defaults read "/Applications/Vibe Island.app/Contents/Info" CFBundleShortVersionString,sw_vers -productVersion); the terminal or app the agent runs in, and where a click landed if it was wrong; which events it sends and from where; the manifest without anything that looks like a secret; what you expected and what happened; the relevantbridge.loglines (grep '"s":"acme-coder"' ~/Library/Logs/VibeIsland/bridge.log | tail -n 40). - It never contains: API keys, tokens or passwords; prompts, replies or other conversation content (including
prompt,last_assistant_messageand the text oftool_input); private file paths or project names the user has not agreed to share.
Reference
Section titled “Reference”Read a section here only when a step above links to it.
Events
Section titled “Events”Event names use the Claude Code spelling. Other common styles are converted: sessionStart, session_start, session-start and SESSION_START are all SessionStart. A name that is not recognized passes through unchanged; use eventMap to translate it. PermissionResult is the exception: it is not converted, so send it exactly as written.
| Event | Useful fields | Effect on the card |
|---|---|---|
SessionStart | session_id, cwd | Creates the card, idle. |
UserPromptSubmit | prompt | Working; prompt becomes the summary. |
PreToolUse | tool_name, tool_input | Working, with the tool name. |
PostToolUse | tool_name | Working. |
PostToolUseFailure | tool_name | Working. |
Stop | last_assistant_message | Idle, with a completion notice when the reply is sent. |
StopFailure | last_assistant_message | Idle. |
SessionEnd | none | The card is removed. |
Notification | notification_type, tool_name, tool_input, message | With permission_prompt, an approval reminder. Any other type returns the card to idle. |
PermissionResult | none | Leaves the approval state; a tool event, Stop or the next prompt also clears it. |
Fields
Section titled “Fields”Send a JSON object on stdin with snake_case names. Other common styles are converted at the top level only (sessionId and session-id become session_id); keys inside tool_input are left alone. An unrecognized name passes through; use fieldMap. Names that start with an underscore are reserved: do not send them.
| Field | Type | Required | Notes |
|---|---|---|---|
hook_event_name | string | Yes | The event name; without it the event is skipped. |
session_id | string | Yes in practice | Identical on every event of a session. If missing, Vibe Island generates an unstable fallback id. See Session ids. |
cwd | string | Recommended | The card’s project name; a hint passed to jump rules. |
prompt | string | No | The user’s message, for UserPromptSubmit. |
tool_name | string | No | For tool events and permission reminders. |
tool_input | object | No | Tool arguments such as {"command":"ls"}; a short summary shows on the card. |
last_assistant_message | string | No | The reply, for Stop and StopFailure; long text is shortened. |
notification_type | string | No | permission_prompt for approval reminders. |
message | string | No | Free text for Notification. |
Session lifecycle and cleanup
Section titled “Session lifecycle and cleanup”Stopends a turn; the next turn updates the same card.SessionEndremoves the card, and a late event for that id can bring it back. The command process exiting does not end the session.- A card goes away only when
SessionEndarrives, the user removes it, a remote host’s SSH connection drops, or idle cleanup removes it: a card idle (not working) for longer than the cleanup time, 2 hours by default, set in Settings > General > Idle session cleanup (30 minutes to 24 hours, or off). - A session that shows as working but sends no event for 4 hours returns to waiting for input; the card stays. Send tool events during long tasks.
- A new session with the same source in the same terminal tab replaces the older card. After Vibe Island restarts, earlier custom sessions are not restored; one reappears with its next event.
Approval reminders
Section titled “Approval reminders”If your agent has its own approval flow, tell Vibe Island that the user has to go back and approve, with the “Needs approval” command in Step 4. It is a reminder only: nothing is returned, and your agent decides in its own interface.
The card shows waiting for approval with the tool name and a short summary; a click takes the user back to your agent (see Jumping back to your app). The next event your agent sends (a tool event, PermissionResult, Stop, StopFailure, UserPromptSubmit or SessionEnd) clears it. Send one reminder per approval, and do not send PermissionRequest; it is not part of this interface.
stdout behavior
Section titled “stdout behavior”Every call prints one line, {} or your stdoutResponse, the same for every event and whether or not it was delivered. Empty or invalid stdin prints {} and sends nothing; if the app cannot be found nothing is printed. The exit code is 0 except for a missing --source (1). Delivery is one-way and best effort.
Ignore stdout. If your host puts hook stdout into a model’s context or retries on some output, set stdoutResponse to something harmless, such as an empty string. If your code does read it, treat anything it does not explicitly recognize as “no decision”: its content may grow in future versions.
Session ids
Section titled “Session ids”You send session_id; Vibe Island prefixes it with your source name, so {session_id} in a jump rule is <source>-<id>: demo-1 becomes acme-coder-demo-1, and an id that already starts with acme-coder- is not prefixed twice. {raw_session_id} is exactly what you sent (demo-1), on this Mac and for remote hosts; use it to find the session in your own app. Two ids that differ only by the prefix are the same session.
Jumping back to your app
Section titled “Jumping back to your app”Verified on a real Mac with the build that ships this feature:
- A command-line agent in Terminal.app, iTerm2 or Ghostty: clicking its card lands on the window and tab the agent runs in. That includes a minimized iTerm2 window (restored and focused), several tabs in one iTerm2 or Ghostty window, and a tmux pane inside Ghostty.
- An agent that is its own macOS app, without a jump rule: clicking its card brings that app to the front.
- The same app with a jump rule in its manifest: the app is brought to the front and then receives the URL.
{raw_session_id}is the id the developer sent and{session_id}is<name>-<id>.
Known issues in 1.0.53:
- Ghostty with several tabs running the same custom agent: a click can land on another tab of that agent when the session ids start with the same characters (
chat-1,chat-2), or when the source name is longer than 12 characters. Random UUIDs as session ids and a source name of 12 characters or fewer avoid it. - When a terminal tab is closed and the agent did not send
SessionEnd, its card stays until idle cleanup or until the user removes it. - A jump rule whose URL scheme no installed app handles makes macOS show a dialog saying no application is set to open the URL.
Not yet verified for custom agents: WezTerm, kitty, Warp, Electron-style apps where the agent runs in a helper process, and agents on SSH remotes. Ask the user to report what they see.
To land on the exact conversation inside your own app, add bundleIdentifier and jumpRule to the manifest and register a URL scheme in your app; Vibe Island opens it with the session hints filled in (variables and a handler example: Custom Jump Rules). When you test a jump rule, note that macOS only routes a custom URL scheme to an app that is properly installed and signed. An unsigned build run from a temporary folder does not receive the URL even though Vibe Island sends it, so test with a normally built app in a normal location.
Remote hosts (SSH)
Section titled “Remote hosts (SSH)”Agents on a host connected through Vibe Island SSH Remote can appear in the same notch; this has not been verified for custom agents yet. The manifest keys eventMap, fieldMap, terminatingTools and stdoutResponse do not apply to them, so send standard names. Remote cards are removed when the SSH connection drops. For remote setup, email [email protected].
What works today, planned, not supported
Section titled “What works today, planned, not supported”What is listed under Works today is what we support.
- Works today: live status, tool name and completion notice; your own name as a text tag or a first-letter badge; a click on the card returns to the terminal or app the agent runs in; read-only approval reminders; the optional manifest (display name, event and field mapping, automatic turn end for bots, jump rule).
- Planned: answer approvals and questions from the notch.
- Not supported: sub-sessions of an agent (no parent and child grouping); an agent’s own usage or quota source; sending messages to the agent from the notch; a bypass button; automatic session naming; custom icons (the name shows as a text tag or a first-letter badge in a neutral color); restoring sessions after Vibe Island restarts; the Mac App Store edition.
Compatibility
Section titled “Compatibility”The event names, field names and manifest keys in this guide are stable. They are not removed or renamed and their meaning does not change. New abilities arrive as new events, fields or manifest keys, so an integration written against this guide keeps working.