From 1ced3fbb88a23b7379b7feccdf72be3582f60032 Mon Sep 17 00:00:00 2001 From: Tyler Brown Date: Wed, 11 Nov 2026 17:28:27 -0800 Subject: [PATCH 01/13] Give each worker question its own buttons; add the approval card check and tidier --- main/src/services/notify/cardCheck.test.ts | 63 ++++++++ main/src/services/notify/cardCheck.ts | 110 +++++++++++++ main/src/services/notify/cardTidy.test.ts | 69 ++++++++ main/src/services/notify/cardTidy.ts | 153 ++++++++++++++++++ main/src/services/notify/notifyQueue.ts | 8 +- main/src/services/notify/notifyService.ts | 1 + .../src/services/notify/workerContext.test.ts | 17 +- main/src/services/notify/workerContext.ts | 30 +++- shared/types/notify.ts | 2 + 9 files changed, 442 insertions(+), 11 deletions(-) create mode 100644 main/src/services/notify/cardCheck.test.ts create mode 100644 main/src/services/notify/cardCheck.ts create mode 100644 main/src/services/notify/cardTidy.test.ts create mode 100644 main/src/services/notify/cardTidy.ts diff --git a/main/src/services/notify/cardCheck.test.ts b/main/src/services/notify/cardCheck.test.ts new file mode 100644 index 000000000..46099e1aa --- /dev/null +++ b/main/src/services/notify/cardCheck.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; +import { PaneError } from '../../../../shared/paneError'; +import { assertCard, checkCard } from './cardCheck'; + +const subtitle = 'The one-day check passed: option A holds. Approving the cover sheet needs this answer.'; +const choices = [{ label: 'Keep it', detail: 'They lose only this account’s orgs.', recommended: true }, { label: 'Sign them out', detail: 'Matches today’s docs.' }]; + +describe('checking an approval card', () => { + it('accepts a card that keeps the contract, with no warnings', () => { + expect(checkCard({ title: 'Okta sign-out: keep other orgs’ sessions?', context: subtitle, choices })).toEqual({ warnings: [] }); + }); + + it('refuses a title over 160 characters, saying what happened and what to do', () => { + const result = checkCard({ title: 'x'.repeat(200), context: subtitle, choices }); + expect(result.refusal?.code).toBe('ERR_NOTIFY_TITLE'); + expect(result.refusal?.message).toContain('200 characters'); + expect(result.refusal?.next).toMatch(/--context/u); + }); + + it('refuses a title with a line break', () => { + expect(checkCard({ title: 'Ship it?\nCI is green.', context: subtitle, choices }).refusal?.code).toBe('ERR_NOTIFY_TITLE'); + }); + + it('refuses two recommended options, in choices or in any one question', () => { + const both = choices.map(choice => ({ ...choice, recommended: true })); + expect(checkCard({ title: 'Ship it?', context: subtitle, choices: both }).refusal?.code).toBe('ERR_NOTIFY_RECOMMENDED'); + const questions = [{ title: 'First?', choices }, { title: 'Second?', choices: both }]; + const refusal = checkCard({ title: 'Two choices', context: subtitle, questions }).refusal; + expect(refusal?.code).toBe('ERR_NOTIFY_RECOMMENDED'); + expect(refusal?.message).toContain('Second?'); + }); + + it('warns, without refusing, on a 120-character title and a card with no subtitle', () => { + const result = checkCard({ title: `${'word '.repeat(23)}ok?`, choices }); + expect(result.refusal).toBeUndefined(); + expect(result.warnings).toEqual([ + expect.stringMatching(/^The title is 1\d\d characters/u), + expect.stringMatching(/^No subtitle/u), + ]); + }); + + it('warns on a long subtitle, no recommendation, too many options, bare options and unlinked paths', () => { + const many = ['A', 'B', 'C', 'D', 'E'].map(label => ({ label })); + const result = checkCard({ title: 'Which build?', context: `${'Long. '.repeat(45)}See /Users/me/repo/plan.html.`, choices: many }); + expect(result.warnings.map(warning => warning.split(' ').slice(0, 3).join(' '))).toEqual([ + 'The subtitle is', + 'No option is', + 'A question has', + 'Some options have', + 'These paths aren’t', + ]); + }); + + it('does not warn about a path that is linked', () => { + const result = checkCard({ title: 'Approve the plan?', context: 'Cover sheet r2 at /b/cover-sheet.html is ready.', choices, links: [{ label: 'Cover sheet', path: '/b/cover-sheet.html' }] }); + expect(result.warnings).toEqual([]); + }); + + it('throws the refusal as a PaneError with a code and next step', () => { + expect(() => assertCard({ title: 'x'.repeat(161) })).toThrow(PaneError); + expect(assertCard({ title: 'Ship it?', context: subtitle })).toEqual([]); + }); +}); diff --git a/main/src/services/notify/cardCheck.ts b/main/src/services/notify/cardCheck.ts new file mode 100644 index 000000000..7f0063b37 --- /dev/null +++ b/main/src/services/notify/cardCheck.ts @@ -0,0 +1,110 @@ +import { PaneError } from '../../../../shared/paneError'; +import type { NotifyChoice, NotifyLink, NotifySubQuestion } from '../../../../shared/types/notify'; + +/** + * The approval card contract (D-342.2, D-347): a title of about 80 to 100 characters on one line, + * a subtitle (the context) of one or two sentences up to 240 characters, 2 to 4 options each with + * a consequence and one recommended, and linked references. Pane refuses only the two clear + * mistakes (a title over 160 characters or with a line break, and two recommended options in one + * question) and warns on the rest. Every way of asking reaches this: runpane notify, runpane + * report and the MCP notify tool. + */ + +/** A title past this is refused. */ +export const CARD_TITLE_REFUSE = 160; +/** A title past this gets a warning. */ +export const CARD_TITLE_WARN = 100; +/** A subtitle past this gets a warning. */ +export const CARD_SUBTITLE_WARN = 240; +/** More options than this get a warning. */ +export const CARD_OPTIONS_WARN = 4; + +export interface CardFields { + title: string; + context?: string; + choices?: NotifyChoice[]; + questions?: NotifySubQuestion[]; + links?: NotifyLink[]; +} + +export interface CardRefusal { + code: 'ERR_NOTIFY_TITLE' | 'ERR_NOTIFY_RECOMMENDED'; + message: string; + next: string; +} + +export interface CardCheck { + refusal?: CardRefusal; + warnings: string[]; +} + +const ABSOLUTE_PATH = /(?:^|[\s(“"'`])(\/(?:[\w.@+~-]+\/)+[\w.@+~-]+(?:#[\w.-]+)?)/gu; + +/** Code points, as the CLIs count them. */ +function length(text: string): number { + return [...text].length; +} + +/** The questions a card asks, each with its choices: one for `choices`, several for `questions`. */ +function asked(card: CardFields): Array<{ title: string; choices: NotifyChoice[] }> { + if (card.questions?.length) return card.questions; + return card.choices?.length ? [{ title: card.title, choices: card.choices }] : []; +} + +/** Absolute paths in the text that no link opens. */ +export function unlinkedPaths(text: string, links: NotifyLink[] | undefined): string[] { + const linked = new Set((links ?? []).flatMap(link => ('path' in link ? [link.path, link.path.replace(/#.*$/u, '')] : []))); + const found = [...text.matchAll(ABSOLUTE_PATH)].map(match => match[1].replace(/[.,;:]+$/u, '')); + return [...new Set(found)].filter(found => !linked.has(found) && !linked.has(found.replace(/#.*$/u, ''))); +} + +/** The refusal, if the card breaks the contract outright, and a warning for each softer miss. */ +export function checkCard(card: CardFields): CardCheck { + const title = card.title; + const titleLength = length(title); + if (/[\r\n]/u.test(title)) { + return { refusal: { + code: 'ERR_NOTIFY_TITLE', + message: 'The title has a line break, so the card was not posted. A title is one line saying what you need.', + next: 'Keep the first line as --title (about 80 to 100 characters) and move the rest to --context, the one- or two-sentence subtitle; put each question in --questions or --choices.', + }, warnings: [] }; + } + if (titleLength > CARD_TITLE_REFUSE) { + return { refusal: { + code: 'ERR_NOTIFY_TITLE', + message: `The title is ${titleLength} characters, past the ${CARD_TITLE_REFUSE} a card takes, so it was not posted.`, + next: 'Shorten --title to about 80 to 100 characters saying what you need, put why it matters in --context (one or two sentences), each question in --questions or --choices, and files in --links.', + }, warnings: [] }; + } + for (const question of asked(card)) { + const recommended = question.choices.filter(choice => choice.recommended); + if (recommended.length > 1) { + const where = card.questions?.length ? `"${question.title}"` : 'This card'; + return { refusal: { + code: 'ERR_NOTIFY_RECOMMENDED', + message: `${where} recommends ${recommended.length} options (${recommended.map(choice => choice.label).join(', ')}), so the card was not posted. A question recommends one.`, + next: 'Mark only the option you would pick with "recommended": true, and say why in its detail.', + }, warnings: [] }; + } + } + + const warnings: string[] = []; + if (titleLength > CARD_TITLE_WARN) warnings.push(`The title is ${titleLength} characters; aim for 80 to 100 and move detail into --context.`); + const context = card.context?.trim() ?? ''; + if (!context) warnings.push('No subtitle: add --context with one or two sentences on why it matters.'); + else if (length(context) > CARD_SUBTITLE_WARN) warnings.push(`The subtitle is ${length(context)} characters; keep --context to one or two sentences (up to ${CARD_SUBTITLE_WARN}) and link a Summary page for the rest.`); + const questions = asked(card); + if (questions.some(question => !question.choices.some(choice => choice.recommended))) warnings.push('No option is recommended: mark the one you would pick with "recommended": true.'); + if (questions.some(question => question.choices.length > CARD_OPTIONS_WARN)) warnings.push(`A question has more than ${CARD_OPTIONS_WARN} options; offer 2 to ${CARD_OPTIONS_WARN}.`); + if (questions.some(question => question.choices.some(choice => !choice.detail?.trim()))) warnings.push('Some options have no consequence: give each a "detail" saying what happens if it is picked.'); + const paths = unlinkedPaths([title, context, ...questions.map(question => question.title)].join('\n'), card.links); + if (paths.length) warnings.push(`These paths aren’t linked: ${paths.join(', ')}. Pass them in --links so they open beside the card.`); + return { warnings }; +} + +/** The card's warnings; throws its refusal as a PaneError with a code and next step. */ +export function assertCard(card: CardFields): string[] { + const { refusal, warnings } = checkCard(card); + if (refusal) throw new PaneError(refusal.code, refusal.message, refusal.next); + return warnings; +} diff --git a/main/src/services/notify/cardTidy.test.ts b/main/src/services/notify/cardTidy.test.ts new file mode 100644 index 000000000..3058f2bdd --- /dev/null +++ b/main/src/services/notify/cardTidy.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; +import { tidyCard } from './cardTidy'; +import { checkCard } from './cardCheck'; + +/** Q-42 as it was sent: one --question holding the title, two questions, their recommendations and two files. */ +const q42 = "Q-42: Okta sign-out rework (option A holds; cover sheet ready for approval). 1) With Require SSO off, should a person Okta removes keep a password session for their other orgs? Recommended: yes, they keep it and lose the account's orgs; docs stop saying 'signs them out on every device'. 2) Should Require SSO end password sessions for everyone on the domain (same as sign-in, recommended) or only the account's members? Files: /Users/tb/repos/bloom-mono/worktrees/okta-sso-account/tmp/greenfield/okta-signout-rework/decisions.html#q-42, /Users/tb/repos/bloom-mono/worktrees/okta-sso-account/tmp/greenfield/okta-signout-rework/cover-sheet.html"; +const q42Context = 'One-day check done: option A holds (5/5 pass, 2 with caveats absorbed by the plan). Cover sheet r1 ready for approval; approval needs Q-42.'; + +/** Every word of the tidied card, so a test can prove none was added. */ +function words(text: string): string[] { + return text.toLowerCase().match(/[\p{L}\p{N}']+/gu) ?? []; +} + +describe('tidying an old paragraph card', () => { + it('splits Q-42 into a short title, two questions with buttons and two links', () => { + const tidied = tidyCard({ title: q42, context: q42Context }); + expect(tidied?.card.title).toBe('Q-42: Okta sign-out rework (option A holds; cover sheet ready for approval)'); + expect(tidied?.card.context).toBe(q42Context); + expect(tidied?.card.questions).toEqual([ + { + title: 'With Require SSO off, should a person Okta removes keep a password session for their other orgs?', + choices: [ + { label: 'Yes', detail: "they keep it and lose the account's orgs; docs stop saying 'signs them out on every device'", recommended: true }, + { label: 'No' }, + ], + }, + { + title: "Should Require SSO end password sessions for everyone on the domain or only the account's members?", + choices: [ + { label: 'Everyone on the domain', detail: 'same as sign-in', recommended: true }, + { label: "Only the account's members" }, + ], + }, + ]); + expect(tidied?.card.links).toEqual([ + { label: 'decisions.html', path: '/Users/tb/repos/bloom-mono/worktrees/okta-sso-account/tmp/greenfield/okta-signout-rework/decisions.html#q-42' }, + { label: 'cover-sheet.html', path: '/Users/tb/repos/bloom-mono/worktrees/okta-sso-account/tmp/greenfield/okta-signout-rework/cover-sheet.html' }, + ]); + expect(tidied?.asSent).toEqual({ title: q42, context: q42Context }); + expect(checkCard(tidied!.card).refusal).toBeUndefined(); + }); + + it('adds no word that was not sent, beyond the Yes and No a yes-or-no question offers', () => { + const tidied = tidyCard({ title: q42, context: q42Context })!; + const sent = new Set(words(`${q42} ${q42Context}`)); + const shown = [tidied.card.title, tidied.card.context ?? '', ...(tidied.card.questions ?? []).flatMap(q => [q.title, ...q.choices.flatMap(c => [c.label, c.detail ?? ''])]), ...(tidied.card.links ?? []).map(l => l.label)].join(' '); + expect(words(shown).filter(word => !sent.has(word))).toEqual(['no']); + }); + + it('turns "A or B (recommended)" in a single question into choices', () => { + const tidied = tidyCard({ title: 'Release notes are drafted, the changelog is reviewed and CI is green on main for every platform we ship. Ship the release tonight (recommended) or wait for the Intel build?' }); + expect(tidied?.card).toEqual({ + title: 'Release notes are drafted, the changelog is reviewed and CI is green on main for every platform we ship', + context: 'Ship the release tonight or wait for the Intel build?', + choices: [{ label: 'Ship the release tonight', recommended: true }, { label: 'Wait for the Intel build' }], + }); + }); + + it('leaves a well-formed card alone, and a long one it cannot split cleanly', () => { + expect(tidyCard({ title: 'Ship the release tonight?', context: 'CI is green.' })).toBeUndefined(); + expect(tidyCard({ title: 'x'.repeat(200) })).toBeUndefined(); + expect(tidyCard({ title: 'Ship it?\nCI is green and the notes are drafted.' })).toBeUndefined(); + expect(tidyCard({ title: `${'A long sentence about the plan that keeps going. '.repeat(4)}What now?` })).toBeUndefined(); + }); + + it('leaves a card that already has buttons alone', () => { + expect(tidyCard({ title: q42, choices: [{ label: 'Ok' }] })).toBeUndefined(); + }); +}); diff --git a/main/src/services/notify/cardTidy.ts b/main/src/services/notify/cardTidy.ts new file mode 100644 index 000000000..9c7fb284f --- /dev/null +++ b/main/src/services/notify/cardTidy.ts @@ -0,0 +1,153 @@ +import path from 'path'; +import { NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyChoice, type NotifyLink, type NotifySubQuestion } from '../../../../shared/types/notify'; +import { CARD_TITLE_REFUSE, unlinkedPaths, type CardFields } from './cardCheck'; + +/** + * Tidying an old paragraph card (D-345, Q-343.2): some agents still send a whole question as one + * long title, like Q-42. When a card's title breaks the contract and the card has no buttons, + * Pane splits it by fixed rules, never a model, and never adds a word: + * + * - the title is cut at its first sentence; + * - "1) … 2) …" become separate questions; + * - "Recommended: yes|no" after a yes-or-no question, and "A or B (recommended)", become options; + * - a "Files:" list and absolute paths become links. + * + * It runs only when every question splits into options; otherwise the card stays as sent (and + * the check refuses it). The text as sent is kept for Show as sent. + */ + +export interface TidiedCard { + card: CardFields; + /** The title and context exactly as they were sent. */ + asSent: { title: string; context?: string }; +} + +const SENTENCE_END = /([.?!])(?=\s+(?:[\p{Lu}\p{N}]|$))/u; +const NUMBERED = /(?:^|\s)(\d)\)\s+/gu; +const FILES = /\s*\bFiles?:\s*(.+)$/su; +const RECOMMENDED_YES_NO = /^\s*Recommended:\s*(yes|no)\b[\s,;:.-]*(.*)$/isu; +const PARENTHETICAL = /\s*\(([^()]*\brecommended\b[^()]*)\)/iu; +const LINKABLE = /^(?:\/|https?:\/\/)\S+$/u; + +function capitalized(text: string): string { + return text.charAt(0).toUpperCase() + text.slice(1); +} + +function withoutStop(text: string): string { + return text.trim().replace(/[.;,]+$/u, '').trim(); +} + +function linkOf(target: string): NotifyLink { + if (/^https?:\/\//u.test(target)) return { label: target.replace(/^https?:\/\//u, '').replace(/\/$/u, '').slice(0, 80), url: target }; + return { label: (path.basename(target.replace(/#.*$/u, '')) || target).slice(0, 80), path: target }; +} + +/** The first sentence, without its full stop, and what follows. Undefined when there is only one. */ +function firstSentence(text: string): { head: string; rest: string } | undefined { + // A parenthesis can hold a sentence end ("(…; cover sheet ready.)"): skip ends inside one. + let depth = 0; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (char === '(') depth += 1; + else if (char === ')') depth = Math.max(0, depth - 1); + else if (depth === 0 && SENTENCE_END.test(text.slice(index, index + 3)) && /[.?!]/u.test(char)) { + const rest = text.slice(index + 1).trim(); + if (!rest) return undefined; + return { head: char === '.' ? text.slice(0, index).trim() : text.slice(0, index + 1).trim(), rest }; + } + } + return undefined; +} + +/** The question and its options, when the text says them in one of the fixed patterns. */ +function questionOf(text: string): NotifySubQuestion | undefined { + const mark = text.indexOf('?'); + if (mark < 0) return undefined; + const asked = text.slice(0, mark).trim(); + const after = text.slice(mark + 1); + const parenthetical = PARENTHETICAL.exec(asked); + if (parenthetical) { + // "A (…, recommended) or B?" or "A or B (recommended)?" + const plain = asked.replace(PARENTHETICAL, ''); + const or = plain.lastIndexOf(' or '); + if (or < 0) return undefined; + const left = plain.slice(0, or).trim(); + const right = plain.slice(or + 4).trim(); + const rightWords = right.split(/\s+/u); + const leftWords = left.split(/\s+/u); + const a = leftWords.slice(-Math.min(rightWords.length, leftWords.length)).join(' '); + if (!a || !right) return undefined; + const detail = withoutStop(parenthetical[1].replace(/\brecommended\b/iu, '').replace(/^[\s,;:-]+|[\s,;:-]+$/gu, '')); + const onLeft = parenthetical.index < asked.lastIndexOf(' or '); + const recommended: NotifyChoice = detail ? { label: '', detail, recommended: true } : { label: '', recommended: true }; + const choices: NotifyChoice[] = onLeft + ? [{ ...recommended, label: capitalized(a) }, { label: capitalized(right) }] + : [{ label: capitalized(a) }, { ...recommended, label: capitalized(right) }]; + return { title: `${plain.trim()}?`, choices }; + } + const yesNo = RECOMMENDED_YES_NO.exec(after); + if (yesNo) { + const yes = yesNo[1].toLowerCase() === 'yes'; + const detail = withoutStop(yesNo[2]); + const picked: NotifyChoice = detail ? { label: yes ? 'Yes' : 'No', detail, recommended: true } : { label: yes ? 'Yes' : 'No', recommended: true }; + return { title: `${asked}?`, choices: yes ? [picked, { label: 'No' }] : [{ label: 'Yes' }, picked] }; + } + return undefined; +} + +/** The numbered parts of "1) … 2) …", in order from 1, or undefined. */ +function numbered(text: string): { before: string; parts: string[] } | undefined { + const marks = [...text.matchAll(NUMBERED)]; + if (marks.length < 2 || marks.some((mark, index) => Number(mark[1]) !== index + 1)) return undefined; + const parts = marks.map((mark, index) => text.slice((mark.index ?? 0) + mark[0].length, marks[index + 1]?.index ?? text.length).trim()); + return { before: text.slice(0, marks[0].index ?? 0).trim(), parts }; +} + +/** The card split by the fixed rules, or undefined when it keeps the contract or can't be split cleanly. */ +export function tidyCard(card: CardFields): TidiedCard | undefined { + const title = card.title; + if (card.choices?.length || card.questions?.length) return undefined; + if (!/[\r\n]/u.test(title) && [...title].length <= CARD_TITLE_REFUSE) return undefined; + let text = title.replace(/\s+/gu, ' ').trim(); + + const links: NotifyLink[] = []; + const files = FILES.exec(text); + if (files) { + const targets = files[1].split(/,\s+|\s+and\s+/u).map(target => target.trim().replace(/[.;]+$/u, '')); + if (!targets.every(target => LINKABLE.test(target))) return undefined; + links.push(...targets.map(linkOf)); + text = text.slice(0, files.index).trim(); + } + + const split = firstSentence(text); + if (!split) return undefined; + const contextParts: string[] = []; + let choices: NotifyChoice[] | undefined; + let questions: NotifySubQuestion[] | undefined; + const list = numbered(split.rest); + if (list) { + const asked = list.parts.map(questionOf); + if (asked.some(question => !question) || asked.length > NOTIFY_QUESTIONS_MAX) return undefined; + if (list.before) contextParts.push(list.before); + questions = asked.filter((question): question is NotifySubQuestion => question !== undefined); + } else { + const question = questionOf(split.rest); + if (!question) return undefined; + contextParts.push(question.title); + choices = question.choices; + } + if (card.context?.trim()) contextParts.push(card.context.trim()); + + const context = contextParts.join('\n\n'); + for (const found of unlinkedPaths([split.head, context].join('\n'), links)) links.push(linkOf(found)); + const tidied: CardFields = { title: split.head }; + if (context) tidied.context = context; + if (choices) tidied.choices = choices; + if (questions) tidied.questions = questions; + const allLinks = [...links, ...(card.links ?? [])].slice(0, NOTIFY_LINKS_MAX); + if (allLinks.length) tidied.links = allLinks; + if ([...tidied.title].length > CARD_TITLE_REFUSE) return undefined; + const asSent: TidiedCard['asSent'] = { title }; + if (card.context !== undefined) asSent.context = card.context; + return { card: tidied, asSent }; +} diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index 3ee4c141e..5706fcaa3 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -5,6 +5,7 @@ import type { NotifyAgentAction, NotifyAnswer, NotifyChoice, + NotifySubQuestion, NotifyClosed, NotifyConnected, NotifyInstructionsFile, @@ -138,6 +139,8 @@ export interface WorkerQuestion { context?: string; /** The options its status file's question offers: the card asks with them instead of a text box. */ choices?: NotifyChoice[]; + /** Several questions from its status file, each with its own buttons, instead of `choices`. */ + questions?: NotifySubQuestion[]; } export interface NotifyQueueEnvironment { @@ -472,7 +475,7 @@ export class NotifyQueue extends EventEmitter { if (this.entries.has(question.id) || this.isRewrittenFrom(question.id) || this.env.store?.readItem(question.id)) return; const item: NotifyItem = { id: question.id, - ui: question.choices?.length ? 'options' : 'ask', + ui: question.choices?.length || question.questions?.length ? 'options' : 'ask', title: question.question, source: this.withSession(question.source), state: 'waiting', @@ -483,7 +486,8 @@ export class NotifyQueue extends EventEmitter { if (Date.now() - question.at < relayAfterMs()) item.quiet = true; if (question.links?.length) item.links = question.links; if (question.context) item.context = question.context; - if (question.choices?.length) item.choices = question.choices; + if (question.questions?.length) item.questions = question.questions; + else if (question.choices?.length) item.choices = question.choices; item.connected = question.connected ?? connectedOf(item, this.machine()); const entry: Entry = { item, resumeLoop: false }; this.entries.set(item.id, entry); diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index 7c8d2b38f..ad15b5512 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -255,6 +255,7 @@ export class NotifyService { links: links.length ? links : undefined, context: card.context, choices: card.choices, + questions: card.questions, }); } } diff --git a/main/src/services/notify/workerContext.test.ts b/main/src/services/notify/workerContext.test.ts index 250bd1600..e13242cf5 100644 --- a/main/src/services/notify/workerContext.test.ts +++ b/main/src/services/notify/workerContext.test.ts @@ -49,9 +49,20 @@ describe("a card built from a worker's status JSON", () => { }); }); - it('asks several questions in its context, with no choices, since one set of buttons cannot answer them all', () => { - const card = statusCard(JSON.stringify({ needs_you: { questions: [{ q: 'Id or number?', options: ['Id', 'Number'] }, { q: 'Keep old PDFs?', why: 'They are linked from emails.', options: ['Yes', 'No'] }] } }), 'Two invoice questions'); - expect(card).toEqual({ context: '1. Id or number? (Id / Number)\n2. Keep old PDFs? (Yes / No)\nThey are linked from emails.' }); + it('gives each of several questions its own buttons, with the top-level why as the subtitle', () => { + const card = statusCard(JSON.stringify({ needs_you: { why: 'Invoices ship Friday.', questions: [{ q: 'Id or number?', options: ['Id (recommended)', 'Number'] }, { q: 'Keep old PDFs?', why: 'They are linked from emails.', options: ['Yes', 'No'] }] } }), 'Two invoice questions'); + expect(card).toEqual({ + context: 'Invoices ship Friday.', + questions: [ + { title: 'Id or number?', choices: [{ label: 'Id', recommended: true }, { label: 'Number' }] }, + { title: 'Keep old PDFs?', why: 'They are linked from emails.', choices: [{ label: 'Yes' }, { label: 'No' }] }, + ], + }); + }); + + it('lists several questions in words when one of them has no options to press', () => { + const card = statusCard(JSON.stringify({ needs_you: { questions: [{ q: 'Id or number?', options: ['Id', 'Number'] }, { q: 'Anything else?' }] } }), 'Two invoice questions'); + expect(card).toEqual({ context: '1. Id or number? (Id / Number)\n2. Anything else?' }); }); it('shows one plain line for JSON it does not recognize, even cut short, and never the raw text', () => { diff --git a/main/src/services/notify/workerContext.ts b/main/src/services/notify/workerContext.ts index f1a261426..9915774fc 100644 --- a/main/src/services/notify/workerContext.ts +++ b/main/src/services/notify/workerContext.ts @@ -1,6 +1,6 @@ import path from 'path'; import { boundary, decodeOptionalBoundary, type JsonValue } from '../../../../shared/validation/boundaryDecoder'; -import { NOTIFY_LINKS_MAX, type NotifyChoice, type NotifyLink } from '../../../../shared/types/notify'; +import { NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyChoice, type NotifyLink, type NotifySubQuestion } from '../../../../shared/types/notify'; /** The longest context a card carries; cards clamp it to a few lines with Show more. */ const MAX_CONTEXT = 2_000; @@ -28,6 +28,8 @@ type NeedsYou = NonNullable>; export interface StatusCard { context?: string; choices?: NotifyChoice[]; + /** Several questions, each with its own buttons, when every one has options to press. */ + questions?: NotifySubQuestion[]; links?: NotifyLink[]; } @@ -127,6 +129,12 @@ function linkOf(file: string): NotifyLink | undefined { return { label: label.slice(0, 80), path: target }; } +function listed(questions: NonNullable): string { + const list = questions.map((question, index) => `${index + 1}. ${question.q?.trim() ?? ''}${question.options?.length ? ` (${question.options.join(' / ')})` : ''}`.trimEnd()); + const whys = questions.map(question => question.why?.trim()).filter((why): why is string => Boolean(why)); + return [...list, ...whys].join('\n'); +} + function cardOf(needsYou: NeedsYou, title: string): StatusCard { const questions = (needsYou.questions ?? []).filter(question => question.q?.trim() || question.options?.length); const parts: string[] = []; @@ -138,11 +146,21 @@ function cardOf(needsYou: NeedsYou, title: string): StatusCard { if (question.why?.trim()) parts.push(question.why.trim()); const choices = question.options ? choicesOf(question.options) : undefined; if (choices) card.choices = choices; + } else if (questions.length > 1 && questions.length <= NOTIFY_QUESTIONS_MAX && questions.every(question => question.q?.trim() && question.options?.length)) { + // Each question gets its own buttons; the needs_you `why` below is the card's subtitle. + const subQuestions = questions.map(question => { + const choices = choicesOf(question.options ?? []); + if (!choices) return undefined; + const why = question.why?.trim(); + const sub: NotifySubQuestion = { title: question.q?.trim() ?? '', choices }; + if (why) sub.why = why; + return sub; + }); + if (subQuestions.every((sub): sub is NotifySubQuestion => sub !== undefined)) card.questions = subQuestions; + else parts.push(listed(questions)); } else if (questions.length > 1) { - // One set of buttons can't answer several questions: they are listed, and answered in words. - const list = questions.map((question, index) => `${index + 1}. ${question.q?.trim() ?? ''}${question.options?.length ? ` (${question.options.join(' / ')})` : ''}`.trimEnd()); - const whys = questions.map(question => question.why?.trim()).filter((why): why is string => Boolean(why)); - parts.push([...list, ...whys].join('\n')); + // Some question has nothing to press: they are listed, and answered in words. + parts.push(listed(questions)); } else if (needsYou.title?.trim() && !sameText(needsYou.title.trim(), title)) { parts.push(needsYou.title.trim()); } @@ -171,7 +189,7 @@ export function statusCard(text: string, title: string): StatusCard | undefined const needsYou = needsYouOf(json, body); if (needsYou) { const card = cardOf(needsYou, title); - if (card.context || card.choices || card.links) return card; + if (card.context || card.choices || card.questions || card.links) return card; } const line = plainLine(json, body); return line ? { context: cut(line) } : {}; diff --git a/shared/types/notify.ts b/shared/types/notify.ts index 40be28002..fb2fc185b 100644 --- a/shared/types/notify.ts +++ b/shared/types/notify.ts @@ -38,6 +38,8 @@ export interface NotifyChoice { export interface NotifySubQuestion { title: string; choices: NotifyChoice[]; + /** Why this question matters, shown under it. Older machines ignore it. */ + why?: string; } /** An options approval asks 2 to this many questions at once; each takes one pick. */ From 3e9a08fab6883924d0e8ec8c3c11ded383178f41 Mon Sep 17 00:00:00 2001 From: Tyler Brown Date: Wed, 11 Nov 2026 17:31:35 -0800 Subject: [PATCH 02/13] Check and tidy every card on notify, relays, --update and report; return warnings --- main/src/ipc/notify.test.ts | 58 ++++++++++++++++++- main/src/ipc/notify.ts | 46 ++++++++++++++- main/src/ipc/runpane.ts | 5 +- main/src/services/notify/cardAdmit.ts | 22 +++++++ main/src/services/notify/notifyQueue.ts | 7 +++ main/src/services/notify/notifyService.ts | 8 ++- .../src/services/notify/workerContext.test.ts | 25 +++++++- main/src/services/notify/workerContext.ts | 20 +++++++ shared/types/notify.ts | 10 +++- shared/types/runpaneOrchestration.ts | 2 + 10 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 main/src/services/notify/cardAdmit.ts diff --git a/main/src/ipc/notify.test.ts b/main/src/ipc/notify.test.ts index ccf4dede2..66c1c52db 100644 --- a/main/src/ipc/notify.test.ts +++ b/main/src/ipc/notify.test.ts @@ -80,7 +80,7 @@ describe('Notify across machines, through the registered handlers', () => { it('lets another device read the queue, answer it in its own name, and be refused once it is answered', async () => { const host = await startPane(); - const call = host.registry.invoke('runpane:notify', [{ ui: 'approve', title: 'Apply migration 0042?', caller: { cwd: '/repo/api' } }]); + const call = host.registry.invoke('runpane:notify', [{ ui: 'approve', title: 'Apply migration 0042?', context: 'It adds the invoices index; nothing is dropped.', caller: { cwd: '/repo/api' } }]); await flush(); const state = await host.registry.invoke('notify:remote-snapshot', []); expect(state.items).toMatchObject([{ state: 'waiting', source: { name: 'api' } }]); @@ -605,6 +605,62 @@ describe('Approvals items, through the registered handlers', () => { pane.notify.dispose(); }); + it("gives a worker's two status-file questions their own buttons, with its why as the subtitle (J1)", async () => { + const item = blocked(Date.now()); + const status = { state: 'blocked', needs_you: { id: 'Q-42', why: 'Option A holds; approving the cover sheet needs these two answers.', questions: [ + { q: 'Keep their password session for other orgs?', options: ['Keep it (recommended)', 'Sign them out everywhere'] }, + { q: 'Whose password sessions end?', why: 'Same rule as sign-in.', options: ['Everyone on the domain (recommended)', 'Only the account members'] }, + ] } }; + const report = { ...item.report, state: 'blocked' as const, reportedAt: item.at, question: 'Q-42: Okta sign-out: two choices before the cover sheet', summary: JSON.stringify(status) }; + const pane = await startWithSessions(inboxOf([{ ...item, report }])); + const [card] = pane.notify.snapshot().items; + expect(card).toMatchObject({ + ui: 'options', + title: 'Q-42: Okta sign-out: two choices before the cover sheet', + context: 'Option A holds; approving the cover sheet needs these two answers.', + questions: [ + { title: 'Keep their password session for other orgs?', choices: [{ label: 'Keep it', recommended: true }, { label: 'Sign them out everywhere' }] }, + { title: 'Whose password sessions end?', why: 'Same rule as sign-in.', choices: [{ label: 'Everyone on the domain', recommended: true }, { label: 'Only the account members' }] }, + ], + }); + expect(card.choices).toBeUndefined(); + pane.notify.dispose(); + }); + + it('refuses a 200-character title, a title with a line break and two recommended options, and warns on a long title or no subtitle (J2)', async () => { + const pane = await startWithSessions(); + const caller = { paneId: 'loose', panelId: 'loose-panel' }; + const choices = [{ label: 'Ship', detail: 'Goes out tonight.', recommended: true }, { label: 'Wait', detail: 'Ships Monday.' }]; + const refused = async (request: Record) => pane.registry.invoke('runpane:notify', [{ ui: 'options', caller, choices, context: 'CI is green.', ...request }]).then(() => undefined, (error: unknown) => error); + expect(await refused({ title: 'x'.repeat(200) })).toMatchObject({ code: 'ERR_NOTIFY_TITLE', next: expect.stringContaining('--context') }); + expect(await refused({ title: 'Ship it?\nCI is green.' })).toMatchObject({ code: 'ERR_NOTIFY_TITLE' }); + expect(await refused({ title: 'Ship it?', choices: choices.map(choice => ({ ...choice, recommended: true })) })).toMatchObject({ code: 'ERR_NOTIFY_RECOMMENDED' }); + expect(pane.notify.snapshot().items).toEqual([]); + + const long = pane.registry.invoke('runpane:notify', [{ ui: 'options', caller, choices, title: `${'word '.repeat(23)}ok?`, returnAfterMs: 1 }]); + const result = await long; + expect(result.warnings).toEqual([expect.stringMatching(/^The title is 11\d characters/u), expect.stringMatching(/^No subtitle/u)]); + expect(pane.notify.snapshot().items).toHaveLength(1); + pane.notify.dispose(); + }); + + it("tidies Q-42's paragraph into a title, two questions with buttons and links, and keeps it as sent (J3)", async () => { + const pane = await startWithSessions(); + const q42 = "Q-42: Okta sign-out rework (option A holds; cover sheet ready for approval). 1) With Require SSO off, should a person Okta removes keep a password session for their other orgs? Recommended: yes, they keep it and lose the account's orgs; docs stop saying 'signs them out on every device'. 2) Should Require SSO end password sessions for everyone on the domain (same as sign-in, recommended) or only the account's members? Files: /work/okta/decisions.html#q-42, /work/okta/cover-sheet.html"; + void pane.registry.invoke('runpane:notify', [{ ui: 'ask', title: q42, context: 'One-day check done.', caller: { paneId: 'loose', panelId: 'loose-panel' } }]); + await flush(); + const [card] = pane.notify.snapshot().items; + expect(card).toMatchObject({ + ui: 'options', + title: 'Q-42: Okta sign-out rework (option A holds; cover sheet ready for approval)', + context: 'One-day check done.', + questions: [{ choices: [{ label: 'Yes', recommended: true }, { label: 'No' }] }, { choices: [{ label: 'Everyone on the domain', recommended: true }, { label: "Only the account's members" }] }], + links: [{ label: 'decisions.html', path: '/work/okta/decisions.html#q-42' }, { label: 'cover-sheet.html', path: '/work/okta/cover-sheet.html' }], + asSent: { title: q42, context: 'One-day check done.' }, + }); + pane.notify.dispose(); + }); + it('files a card under the Session the worker reported to, even after the worker moved, and closes it when that Session closes', async () => { const moved = { ...blocked(Date.now() - 11 * 60_000), sessionId: 'session-2' }; const pane = await startWithSessions(inboxOf([moved])); diff --git a/main/src/ipc/notify.ts b/main/src/ipc/notify.ts index 835bebb17..6be8387ad 100644 --- a/main/src/ipc/notify.ts +++ b/main/src/ipc/notify.ts @@ -23,6 +23,8 @@ import { buildWindowAppearanceOptions } from '../services/appearanceService'; import { normalizeAppearance } from '../../../shared/types/appearance'; import { PaneError, paneErrorText } from '../../../shared/paneError'; import { statusCard } from '../services/notify/workerContext'; +import { admitCard } from '../services/notify/cardAdmit'; +import { checkCard } from '../services/notify/cardCheck'; import { boundary, decodeBoundary, decodeOptionalBoundary } from '../../../shared/validation/boundaryDecoder'; import { NOTIFY_LINK_LABEL_MAX, NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyCommand, type NotifyItem, type NotifyLink, type NotifyQuestion, type NotifyRemoteCommand, type NotifyRequest, type NotifyResult, type NotifySession, type NotifySharedState } from '../../../shared/types/notify'; import type { RunpaneInboxItem } from '../../../shared/types/runpaneOrchestration'; @@ -56,7 +58,7 @@ const choiceSchema = boundary.object({ recommended: boundary.optional(boundary.boolean), was: boundary.optional(boundary.string), }); -const subQuestionSchema = boundary.object({ title: boundary.string, choices: boundary.array(choiceSchema) }); +const subQuestionSchema = boundary.object({ title: boundary.string, choices: boundary.array(choiceSchema), why: boundary.optional(boundary.string) }); const sessionSchema = boundary.object({ id: boundary.nonEmptyString, name: boundary.string, @@ -171,6 +173,7 @@ const notifyItemSchema = boundary.object({ choices: boundary.optional(boundary.array(choiceSchema)), questions: boundary.optional(boundary.array(subQuestionSchema)), replyIsAnswer: boundary.optional(boundary.boolean), + asSent: boundary.optional(boundary.object({ title: boundary.string, context: boundary.optional(boundary.string) })), optionsOnly: boundary.optional(boundary.boolean), detail: boundary.optional(boundary.string), detailLabel: boundary.optional(boundary.string), @@ -423,7 +426,10 @@ export function registerNotifyHandlers(ipcMain: IpcMain, services: NotifyService const teammate = teammatePaneCaller(); // A secret card writes the owner's secret store, so a teammate cannot post one. if (teammate && parsed.ui === 'secret') throw teammateSecretRefusal(teammate, 'ask for a secret'); - return notify.ask(parsed, paneCommandSignal()); + const warnings = admitNotifyCard(parsed); + const result = await notify.ask(parsed, paneCommandSignal()); + // A card that wasn't shown (a worker's, sent back to its orchestrator) has nothing to warn about. + return warnings.length && !('unavailable' in result) ? { ...result, warnings } : result; }); commandRegistry.register('notify:remote-snapshot', async () => notify.sharedState()); @@ -646,6 +652,42 @@ function linkSavedMachines(notify: NotifyService, machines: NotifyMachines): Sav }; } +/** + * The card check every way of asking reaches (P3): a new card, a relayed one and an update are + * tidied when they are old paragraph cards, refused for the two clear mistakes and warned on the + * rest. Leaves the request as Pane posts it and returns the warnings. Info and secret cards, and + * calls that ask nothing (resume, --took), aren't cards to check. + */ +function admitNotifyCard(request: NotifyRequest): string[] { + if (request.resume || request.took !== undefined) return []; + if (request.update !== undefined) { + if (request.title === undefined && !request.choices && !request.questions) return []; + // An update changes only what it passes; the rest is the approval's, checked when it was asked. + const { refusal } = checkCard({ title: request.title ?? '', choices: request.choices, questions: request.questions }); + if (refusal) throw new PaneError(refusal.code, refusal.message, refusal.next); + return []; + } + if (!request.ui || request.ui === 'info' || request.ui === 'secret' || request.title === undefined) return []; + // Only a card asked in words (or with options it hasn't got) is tidied into buttons. + const tidy = request.ui === 'ask' || request.ui === 'options'; + const admitted = admitCard({ title: request.title, context: request.context, choices: request.choices, questions: request.questions, links: request.links }, { tidy }); + if (admitted.asSent) { + const { card } = admitted; + request.ui = 'options'; + request.title = card.title; + if (card.context !== undefined) request.context = card.context; + else delete request.context; + request.choices = card.choices; + request.questions = card.questions; + if (!request.choices) delete request.choices; + if (!request.questions) delete request.questions; + delete request.suggestions; + if (card.links) request.links = card.links; + request.asSent = admitted.asSent; + } + return admitted.warnings; +} + /** Checks a notify call the way the tool's schema promises, naming the field that's wrong. */ function parseNotifyRequest(value: PaneCommandValue): NotifyRequest { let decoded: ReturnType; diff --git a/main/src/ipc/runpane.ts b/main/src/ipc/runpane.ts index e900aee74..b6ef1d4f7 100644 --- a/main/src/ipc/runpane.ts +++ b/main/src/ipc/runpane.ts @@ -55,6 +55,7 @@ import { normalizeAgentReport, readPanelAgentReport, } from '../services/agentReport'; +import { workerCard } from '../services/notify/workerContext'; import { isCliAgentType, resolveAgentTypeFromCommand } from '../services/agents/agentIdentity'; import { assertDirectOpenCodeLaunchCommand, isValidOpenCodeSessionId } from '../services/agents/opencodeLaunch'; import { @@ -1905,6 +1906,8 @@ export function registerRunpaneHandlers( throw new Error(`Panel ${panel.id} belongs to Pane ${panel.sessionId}, not ${normalized.paneId}.`); } const report = normalizeAgentReport(normalized, new Date().toISOString()); + // A blocked report becomes an approval card: it takes the card check before it's recorded (P3). + const warnings = report.state === 'blocked' && report.question ? workerCard(report.summary, report.question).warnings : []; if (panel.type === 'chat') getChatTabManager().setReport(panel.id, report); else await storePanelAgentReport(panel, report); const panelSummary = panelToSummary(panelManager.getPanel(panel.id) ?? panel); @@ -1917,7 +1920,7 @@ export function registerRunpaneHandlers( report: journalAgentReport(report), }); const sessionIds = await services.orchestrationSessionManager?.recordAgentReport(panel.id, report) ?? []; - return { ok: true, paneId: panel.sessionId, panelId: panel.id, report, sessionIds, liveWatchers }; + return { ok: true, paneId: panel.sessionId, panelId: panel.id, report, sessionIds, liveWatchers, ...(warnings.length ? { warnings } : {}) }; }, result => ({ paneId: result.paneId, panelId: result.panelId, ok: result.ok })); }); diff --git a/main/src/services/notify/cardAdmit.ts b/main/src/services/notify/cardAdmit.ts new file mode 100644 index 000000000..938249b8d --- /dev/null +++ b/main/src/services/notify/cardAdmit.ts @@ -0,0 +1,22 @@ +import { assertCard, type CardFields } from './cardCheck'; +import { tidyCard, type TidiedCard } from './cardTidy'; + +/** A card as Pane posts it: tidied when it was an old paragraph card, with the checks' warnings. */ +export interface AdmittedCard { + card: CardFields; + /** Set when Pane tidied it: the title and context exactly as sent (Show as sent). */ + asSent?: TidiedCard['asSent']; + warnings: string[]; +} + +/** + * The one step every way of asking reaches (runpane notify, runpane report, the MCP notify tool): + * an old paragraph card is tidied by rule, then the card is checked. Throws the refusal as a + * PaneError with a code and next step; otherwise returns the card and its warnings. + */ +export function admitCard(fields: CardFields, options: { tidy?: boolean } = {}): AdmittedCard { + const tidied = options.tidy === false ? undefined : tidyCard(fields); + const card = tidied?.card ?? fields; + const warnings = assertCard(card); + return tidied ? { card, asSent: tidied.asSent, warnings } : { card, warnings }; +} diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index 5706fcaa3..bcfd29218 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -141,6 +141,8 @@ export interface WorkerQuestion { choices?: NotifyChoice[]; /** Several questions from its status file, each with its own buttons, instead of `choices`. */ questions?: NotifySubQuestion[]; + /** Set when Pane tidied its question by rule: the question and context as sent. */ + asSent?: NotifyItem['asSent']; } export interface NotifyQueueEnvironment { @@ -486,6 +488,7 @@ export class NotifyQueue extends EventEmitter { if (Date.now() - question.at < relayAfterMs()) item.quiet = true; if (question.links?.length) item.links = question.links; if (question.context) item.context = question.context; + if (question.asSent) item.asSent = question.asSent; if (question.questions?.length) item.questions = question.questions; else if (question.choices?.length) item.choices = question.choices; item.connected = question.connected ?? connectedOf(item, this.machine()); @@ -670,6 +673,10 @@ export class NotifyQueue extends EventEmitter { return Promise.resolve({ unavailable: `There's no waiting worker question ${id}: it was answered or closed, or its worker moved on. Nothing was shown.`, next: 'Check runpane inbox for the answer.' }); } const { title, ...improved } = question; + // The orchestrator's card replaces the worker's: its buttons and Show as sent, not both. + if (improved.questions) delete entry.item.choices; + if (improved.choices) delete entry.item.questions; + if (!improved.asSent) delete entry.item.asSent; Object.assign(entry.item, improved, title.trim() ? { title } : {}); // It reads a reply when the worker's tab can't (D-258). if (by?.panelId) entry.item.relayedBy = { panelId: by.panelId, name: by.session ? `the ${by.session.name} orchestrator` : by.name }; diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index ad15b5512..19ae68c03 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -37,7 +37,7 @@ import type { RunpaneInboxItem } from '../../../../shared/types/runpaneOrchestra import type { ApprovalStore } from '../approvals/approvalStore'; import type { JsonStoreImportOutcome } from '../../database/jsonStoreImport'; import type { SessionInboxEnded } from '../sessionInbox'; -import { workerReportCard } from './workerContext'; +import { workerCard } from './workerContext'; const AGENT_NAMES = new Map([['claude', 'Claude'], ['codex', 'Codex'], ['cursor', 'Cursor'], ['opencode', 'OpenCode']]); const agentName = (agentType: string | undefined): string => (agentType ? AGENT_NAMES.get(agentType) : undefined) ?? 'Agent'; @@ -239,7 +239,8 @@ export class NotifyService { carded.add(once); const session = this.deps.sessionRecord?.(entry.sessionId); // The status file's needs_you, never its JSON: the question's why, its options and files. - const card = workerReportCard(entry.report?.summary, question); + // Tidied when the question is an old paragraph (P8); a report recorded before the check stays as sent. + const card = workerCard(entry.report?.summary, question, { quietly: true }); // D-81: the worker's status file, which its report named, opens beside the card. const statusLink = entry.report?.summaryPath ? [{ label: `${entry.paneName} status`, path: entry.report.summaryPath }] : []; const links = [...statusLink, ...(card.links ?? []).filter(link => !('path' in link) || link.path !== entry.report?.summaryPath)].slice(0, NOTIFY_LINKS_MAX); @@ -247,7 +248,7 @@ export class NotifyService { if (session) source.session = { id: entry.sessionId, name: session.name, paneId: session.paneId }; this.queue.addWorkerQuestion({ id: entry.id, - question, + question: card.title, source, at, // The Session it was filed in, even after the worker has moved or the Session closed. @@ -256,6 +257,7 @@ export class NotifyService { context: card.context, choices: card.choices, questions: card.questions, + asSent: card.asSent, }); } } diff --git a/main/src/services/notify/workerContext.test.ts b/main/src/services/notify/workerContext.test.ts index e13242cf5..2c05508ca 100644 --- a/main/src/services/notify/workerContext.test.ts +++ b/main/src/services/notify/workerContext.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { statusCard, workerReportCard } from './workerContext'; +import { statusCard, workerCard, workerReportCard } from './workerContext'; /** * Q-80's report, as the Session inbox holds it: the worker sent its whole status file as the @@ -95,3 +95,26 @@ describe("a worker card from its report summary", () => { expect(workerReportCard(JSON.stringify({ state: 'blocked', checks: { passed: [] }, needs_you: null }), 'T')).toEqual({}); }); }); + +describe("a worker's card as runpane report posts it", () => { + const status = JSON.stringify({ needs_you: { why: 'Approving needs this.', questions: [{ q: 'Ship tonight?', options: ['Ship (recommended)', 'Wait'] }] } }); + + it('refuses a 200-character question and one with a line break, with a code and next step (J2)', () => { + expect(() => workerCard(status, 'x'.repeat(200))).toThrow(expect.objectContaining({ code: 'ERR_NOTIFY_TITLE', next: expect.any(String) })); + expect(() => workerCard(status, 'Ship it?\nCI is green.')).toThrow(expect.objectContaining({ code: 'ERR_NOTIFY_TITLE' })); + }); + + it('refuses a status-file question that recommends two options', () => { + const two = JSON.stringify({ needs_you: { questions: [{ q: 'Ship?', options: ['Ship (recommended)', 'Wait (recommended)'] }] } }); + expect(() => workerCard(two, 'Ship the release?')).toThrow(expect.objectContaining({ code: 'ERR_NOTIFY_RECOMMENDED' })); + }); + + it('warns on a 120-character question with no subtitle, and posts it', () => { + const card = workerCard(undefined, `${'word '.repeat(23)}ok?`); + expect(card.warnings).toEqual([expect.stringMatching(/^The title is/u), expect.stringMatching(/^No subtitle/u)]); + }); + + it('keeps a card recorded before the check as it was sent when asked quietly', () => { + expect(workerCard(status, 'x'.repeat(200), { quietly: true }).title).toBe('x'.repeat(200)); + }); +}); diff --git a/main/src/services/notify/workerContext.ts b/main/src/services/notify/workerContext.ts index 9915774fc..100514469 100644 --- a/main/src/services/notify/workerContext.ts +++ b/main/src/services/notify/workerContext.ts @@ -1,6 +1,7 @@ import path from 'path'; import { boundary, decodeOptionalBoundary, type JsonValue } from '../../../../shared/validation/boundaryDecoder'; import { NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyChoice, type NotifyLink, type NotifySubQuestion } from '../../../../shared/types/notify'; +import { admitCard, type AdmittedCard } from './cardAdmit'; /** The longest context a card carries; cards clamp it to a few lines with Show more. */ const MAX_CONTEXT = 2_000; @@ -205,3 +206,22 @@ export function workerReportCard(summary: string | undefined, question: string): if (!text) return {}; return statusCard(text, question) ?? { context: cut(text) }; } + +/** + * A worker's question card as Pane posts it: built from its report, tidied when the question is an + * old paragraph, and checked. Throws the check's refusal (a PaneError); `quietly` keeps a card that + * fails as it was sent instead, for reports recorded before the check existed. + */ +export function workerCard(summary: string | undefined, question: string, options: { quietly?: boolean } = {}): StatusCard & { title: string; asSent?: AdmittedCard['asSent']; warnings: string[] } { + const card = workerReportCard(summary, question); + const fields = { title: question, context: card.context, choices: card.choices, questions: card.questions, links: card.links }; + let admitted: AdmittedCard; + try { + admitted = admitCard(fields); + } catch (error) { + if (!options.quietly) throw error; + return { ...card, title: question, warnings: [] }; + } + const { title, context, choices, questions, links } = admitted.card; + return { title, context, choices, questions, links, asSent: admitted.asSent, warnings: admitted.warnings }; +} diff --git a/shared/types/notify.ts b/shared/types/notify.ts index fb2fc185b..ca63fdb34 100644 --- a/shared/types/notify.ts +++ b/shared/types/notify.ts @@ -90,6 +90,11 @@ export interface NotifyQuestion { * answer itself, since the paused turn resumes with it. It reads "Sent to ", with no fix. */ replyIsAnswer?: boolean; + /** + * Set by the host when it tidied an old paragraph card by rule (P8): the title and context + * exactly as they were sent, for Show as sent. The card reads "Tidied by Pane". + */ + asSent?: { title: string; context?: string }; /** * Set by the chat tab bridge for a built-in question that takes only its own choices: typed text * can't answer it and nobody could read it as a reply, so it has no reply box. @@ -147,7 +152,10 @@ export interface NotifyCaller { agent?: string; } -export type NotifyResult = +/** Each notify result may carry the card check's warnings (a long title, no subtitle, …): the card was posted anyway. */ +export type NotifyResult = NotifyResultBody & { warnings?: string[] }; + +export type NotifyResultBody = /** `answers`: one pick per question, when the approval asks several (`answer` joins them with " · "). */ | { answer: string; note?: string; answers?: string[] } /** diff --git a/shared/types/runpaneOrchestration.ts b/shared/types/runpaneOrchestration.ts index 5d6b9ceca..465131b1d 100644 --- a/shared/types/runpaneOrchestration.ts +++ b/shared/types/runpaneOrchestration.ts @@ -1413,6 +1413,8 @@ export interface RunpaneReportResult { * a named-cursor watch that waits later still reads it. Absent from daemons older than the field. */ liveWatchers?: number; + /** A blocked report's card check: what its card misses (a long title, no subtitle, …). It was recorded anyway. */ + warnings?: string[]; } export interface RunpanePanelLastMessageRequest { From 5235d2b8c684852927ccb77bf1bbee0bfc391b55 Mon Sep 17 00:00:00 2001 From: Tyler Brown Date: Wed, 11 Nov 2026 17:32:10 -0800 Subject: [PATCH 03/13] Print the card check's warnings from runpane notify and report (npm and Python) --- .../runpane-py/src/runpane/local_control.py | 6 ++-- packages/runpane-py/src/runpane/notify.py | 18 ++++++++++-- packages/runpane/src/localControl.ts | 9 ++++-- packages/runpane/src/notify.ts | 28 ++++++++++++++++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/runpane-py/src/runpane/local_control.py b/packages/runpane-py/src/runpane/local_control.py index 9a49e5922..a25d2d509 100644 --- a/packages/runpane-py/src/runpane/local_control.py +++ b/packages/runpane-py/src/runpane/local_control.py @@ -1678,8 +1678,10 @@ def describe_report_delivery(result: Dict[str, Any]) -> List[str]: delivered = f" Delivered to {watchers} waiting watcher{'' if watchers == 1 else 's'}." if watchers > 0 else "" panel_id = result.get("panelId") lines = [f"Reported {describe_report(result.get('report') or {})} for panel {panel_id}.{sessions}{delivered}"] + # The blocked question's card check: posted anyway, but worth fixing next time. + warnings = [f"Warning: {warning}" for warning in result.get("warnings") or [] if isinstance(warning, str)] if session_ids or watchers > 0: - return lines + return lines + warnings stored = f"It is stored on the panel (runpane agents status --panel {panel_id})" if live_watchers is None: lines.append(f"No Session recorded this report. {stored}, and only watchers that list agent.report receive it.") @@ -1688,7 +1690,7 @@ def describe_report_delivery(result: Dict[str, Any]) -> List[str]: f"No Session recorded this report and no watcher was waiting for it. {stored}, " "and a watch with a named cursor (runpane watch --as --kinds agent.report) still receives it when it next waits." ) - return lines + return lines + warnings MAX_INBOX_DETAIL_LENGTH = 300 diff --git a/packages/runpane-py/src/runpane/notify.py b/packages/runpane-py/src/runpane/notify.py index 8dbbcfc25..f9619322a 100644 --- a/packages/runpane-py/src/runpane/notify.py +++ b/packages/runpane-py/src/runpane/notify.py @@ -262,7 +262,12 @@ def parse_questions(text: str, allow_was: bool) -> List[Dict[str, Any]]: for index, item in enumerate(value): if not isinstance(item, dict) or not is_text(item.get("title")) or "choices" not in item: raise invalid("questions", f'questions[{index}] must be an object with a non-empty "title" and "choices", a JSON array of 1 to {MAX_CHOICES} choices.') - questions.append({"title": item["title"], "choices": parse_choices(item["choices"], "questions", f"questions[{index}].choices", allow_was)}) + if "why" in item and not isinstance(item["why"], str): + raise invalid("questions", f'questions[{index}].why must be text: why this question matters, shown under it.') + question = {"title": item["title"], "choices": parse_choices(item["choices"], "questions", f"questions[{index}].choices", allow_was)} + if is_text(item.get("why")): + question["why"] = item["why"] + questions.append(question) return questions @@ -336,7 +341,16 @@ def is_text(value: Any) -> bool: def describe_notify_result(result: Dict[str, Any]) -> str: - """One line for people: the answer or reply, Approved or Denied, where an update went, or what to do next.""" + """One line for people: the answer or reply, Approved or Denied, where an update went, or what to do next. + + The card check's warnings follow, one line each: the card was posted anyway. + """ + line = describe_notify_outcome(result) + warnings = [warning for warning in result.get("warnings") or [] if isinstance(warning, str)] + return "\n".join([line, *(f"Warning: {warning}" for warning in warnings)]) + + +def describe_notify_outcome(result: Dict[str, Any]) -> str: def with_note(text: str) -> str: return f"{text}. Note: {result['note']}" if result.get("note") else text diff --git a/packages/runpane/src/localControl.ts b/packages/runpane/src/localControl.ts index 35ebfb01f..b67fa6b2f 100644 --- a/packages/runpane/src/localControl.ts +++ b/packages/runpane/src/localControl.ts @@ -741,6 +741,8 @@ interface ReportResult { sessionIds: string[]; /** Watches waiting when the report was journaled; absent from older daemons. */ liveWatchers?: number; + /** A blocked report's card check: what its card misses; it was recorded anyway. */ + warnings?: string[]; } type PanelLastMessageResult = @@ -1349,6 +1351,7 @@ const reportResultSchema: BoundarySchema = boundary.object({ report: agentReportSchema, sessionIds: boundary.array(boundary.string), liveWatchers: boundary.optional(boundary.number), + warnings: boundary.optional(boundary.array(boundary.string)), }); /** What `runpane:panels:conversation` returns: the recorded conversation and whether Pane is sure the panel is on it. */ interface PanelConversationResult { @@ -3943,13 +3946,15 @@ function describeReportDelivery(result: ReportResult): string[] { const watchers = result.liveWatchers ?? 0; const delivered = watchers > 0 ? ` Delivered to ${watchers} waiting watcher${watchers === 1 ? '' : 's'}.` : ''; const lines = [`Reported ${describeReport(result.report)} for panel ${result.panelId}.${sessions}${delivered}`]; - if (result.sessionIds.length > 0 || watchers > 0) return lines; + // The blocked question's card check: posted anyway, but worth fixing next time. + const warnings = (result.warnings ?? []).map(warning => `Warning: ${warning}`); + if (result.sessionIds.length > 0 || watchers > 0) return [...lines, ...warnings]; const stored = `It is stored on the panel (runpane agents status --panel ${result.panelId})`; lines.push(result.liveWatchers === undefined ? `No Session recorded this report. ${stored}, and only watchers that list agent.report receive it.` : `No Session recorded this report and no watcher was waiting for it. ${stored}, ` + 'and a watch with a named cursor (runpane watch --as --kinds agent.report) still receives it when it next waits.'); - return lines; + return [...lines, ...warnings]; } interface InboxItem { diff --git a/packages/runpane/src/notify.ts b/packages/runpane/src/notify.ts index 4a339e682..69b9e60b8 100644 --- a/packages/runpane/src/notify.ts +++ b/packages/runpane/src/notify.ts @@ -19,6 +19,8 @@ interface NotifyChoice { interface NotifySubQuestion { title: string; choices: NotifyChoice[]; + /** Why this question matters, shown under it. */ + why?: string; } interface NotifyAction { @@ -78,7 +80,10 @@ export interface NotifyRequest extends Partial> { type NotifyTookAs = 'answer' | 'question'; -export type NotifyResult = +/** Any result may carry the card check's warnings: the card was posted anyway. */ +export type NotifyResult = NotifyResultBody & { warnings?: string[] }; + +type NotifyResultBody = | { answer: string; note?: string; answers?: string[] } /** The person typed a reply: an answer or a question. Say which with `--took`. */ | { reply: string; picks?: string[]; id: string; replyId: string; next: string } @@ -116,7 +121,7 @@ const choiceSchema = boundary.object({ recommended: boundary.optional(boundary.boolean), was: boundary.optional(boundary.nonEmptyString), }); -const subQuestionSchema = boundary.object({ title: boundary.nonEmptyString, choices: boundary.json }); +const subQuestionSchema = boundary.object({ title: boundary.nonEmptyString, choices: boundary.json, why: boundary.optional(boundary.string) }); const actionSchema = boundary.object({ label: boundary.nonEmptyString, url: boundary.nonEmptyString }); const linkSchema = boundary.object({ label: boundary.nonEmptyString, @@ -125,7 +130,7 @@ const linkSchema = boundary.object({ approval: boundary.optional(boundary.string), }); const note = boundary.optional(boundary.string); -export const notifyResultSchema: BoundarySchema = boundary.union( +const notifyResultBodySchema: BoundarySchema = boundary.union( boundary.object({ waiting: boundary.literal(true), id: boundary.nonEmptyString, next: boundary.string }), boundary.object({ unavailable: boundary.string, next: boundary.string }), boundary.object({ took: boundary.enumeration('answer', 'question'), id: boundary.nonEmptyString, next: boundary.optional(boundary.string) }), @@ -136,6 +141,14 @@ export const notifyResultSchema: BoundarySchema = boundary.union( boundary.object({ status: boundary.literal('saved'), project: boundary.string, name: boundary.string }), boundary.object({ status: boundary.literal('declined') }), ); +const warningsSchema = boundary.object({ warnings: boundary.optional(boundary.array(boundary.string)) }); +export const notifyResultSchema: BoundarySchema = { + decode(current) { + const body = notifyResultBodySchema.decode(current); + const { warnings } = warningsSchema.decode(current); + return warnings?.length ? { ...body, warnings } : body; + }, +}; function invalid(field: string, problem: string, next = `Fix ${field} and call notify again.`): RunpaneError { return new RunpaneError('ERR_NOTIFY_INVALID', `${problem} Nothing was shown to the person.`, next); @@ -358,7 +371,9 @@ function parseQuestions(text: string, allowWas: boolean): NotifySubQuestion[] { } return value.map((item, index) => { const question = decodeOr(item, subQuestionSchema, invalid('questions', `questions[${index}] must be an object with a non-empty "title" and "choices", a JSON array of 1 to ${MAX_CHOICES} choices.`)); - return { title: question.title, choices: parseChoices(question.choices, 'questions', `questions[${index}].choices`, allowWas) }; + const parsedQuestion: NotifySubQuestion = { title: question.title, choices: parseChoices(question.choices, 'questions', `questions[${index}].choices`, allowWas) }; + if (question.why?.trim()) parsedQuestion.why = question.why; + return parsedQuestion; }); } @@ -391,6 +406,11 @@ function nonEmpty(value: string | undefined): string | undefined { /** One line for people: the answer or reply, Approved or Denied, where an update went, or what to do next. */ export function describeNotifyResult(result: NotifyResult): string { + // The card check's warnings follow, one line each: the card was posted anyway. + return [describeNotifyOutcome(result), ...(result.warnings ?? []).map(warning => `Warning: ${warning}`)].join('\n'); +} + +function describeNotifyOutcome(result: NotifyResult): string { const withNote = (text: string, noteText: string | undefined) => (noteText ? `${text}. Note: ${noteText}` : text); if ('waiting' in result) return `Still waiting (id ${result.id}). ${result.next}`; if ('reply' in result) { From d4cd12ed33f5b77af37494a51711d057fcfdfeed Mon Sep 17 00:00:00 2001 From: Tyler Brown Date: Wed, 11 Nov 2026 17:35:56 -0800 Subject: [PATCH 04/13] Serve each approval's Summary as one page: the asker's own, or Pane's template, with no scripts or network --- main/src/ipc/notify.ts | 14 +- main/src/services/notify/notifyQueue.ts | 5 + main/src/services/notify/notifyService.ts | 13 ++ main/src/services/notify/summaryPage.test.ts | 60 ++++++++ main/src/services/notify/summaryPage.ts | 145 +++++++++++++++++++ main/src/services/notify/workerContext.ts | 8 +- shared/remoteClient/readChannels.ts | 2 + shared/types/machineConnections.ts | 1 + shared/types/notify.ts | 22 +++ 9 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 main/src/services/notify/summaryPage.test.ts create mode 100644 main/src/services/notify/summaryPage.ts diff --git a/main/src/ipc/notify.ts b/main/src/ipc/notify.ts index 6be8387ad..46b1fc759 100644 --- a/main/src/ipc/notify.ts +++ b/main/src/ipc/notify.ts @@ -25,8 +25,9 @@ import { PaneError, paneErrorText } from '../../../shared/paneError'; import { statusCard } from '../services/notify/workerContext'; import { admitCard } from '../services/notify/cardAdmit'; import { checkCard } from '../services/notify/cardCheck'; +import { summaryPage } from '../services/notify/summaryPage'; import { boundary, decodeBoundary, decodeOptionalBoundary } from '../../../shared/validation/boundaryDecoder'; -import { NOTIFY_LINK_LABEL_MAX, NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyCommand, type NotifyItem, type NotifyLink, type NotifyQuestion, type NotifyRemoteCommand, type NotifyRequest, type NotifyResult, type NotifySession, type NotifySharedState } from '../../../shared/types/notify'; +import { NOTIFY_LINK_LABEL_MAX, NOTIFY_LINKS_MAX, NOTIFY_QUESTIONS_MAX, type NotifyCommand, type NotifyItem, type NotifyLink, type NotifyQuestion, type NotifyRemoteCommand, type NotifyRequest, type NotifyResult, type NotifySession, type NotifySharedState, type NotifySummaryPage } from '../../../shared/types/notify'; import type { RunpaneInboxItem } from '../../../shared/types/runpaneOrchestration'; import type { SessionInboxEnded } from '../services/sessionInbox'; import { thisMachineName, type ApprovalStore } from '../services/approvals/approvalStore'; @@ -174,6 +175,8 @@ const notifyItemSchema = boundary.object({ questions: boundary.optional(boundary.array(subQuestionSchema)), replyIsAnswer: boundary.optional(boundary.boolean), asSent: boundary.optional(boundary.object({ title: boundary.string, context: boundary.optional(boundary.string) })), + summaryPage: boundary.optional(boundary.string), + statusFile: boundary.optional(boundary.string), optionsOnly: boundary.optional(boundary.boolean), detail: boundary.optional(boundary.string), detailLabel: boundary.optional(boundary.string), @@ -433,6 +436,14 @@ export function registerNotifyHandlers(ipcMain: IpcMain, services: NotifyService }); commandRegistry.register('notify:remote-snapshot', async () => notify.sharedState()); + // An approval's Summary (P4): the asker's page or Pane's template, served with no scripts or network. + commandRegistry.register('notify:summary-page', async (id: PaneCommandValue, options?: PaneCommandValue): Promise => { + const wanted = decodeBoundary(id, boundary.nonEmptyString); + const theme = decodeOptionalBoundary(options ?? {}, boundary.object({ theme: boundary.optional(boundary.enumeration('light', 'dark')) }))?.theme ?? 'light'; + const item = notify.queue.snapshot().items.find(candidate => candidate.id === wanted && (candidate.machineId ?? 'local') === 'local'); + if (!item) throw new PaneError('ERR_NOTIFY_GONE', `There's no approval ${wanted} on this machine any more, so it has no Summary to show. Nothing was changed.`, 'Open the approval again from Approvals.'); + return summaryPage(item, theme); + }); commandRegistry.register('notify:remote-command', async (command: PaneCommandValue, sender: PaneCommandValue) => { // A Mac names itself "studio.local" on its event stream; cards call it "studio". const device = decodeBoundary(sender, remoteSenderSchema).device.replace(/\.(local|lan|home|internal)$/i, ''); @@ -773,6 +784,7 @@ function readStatusContext(request: NotifyRequest, newCard: boolean): void { request.ui = 'options'; request.choices = card.choices; } + if (card.summaryPage) request.summaryPage = request.caller.cwd && !path.isAbsolute(card.summaryPage) ? path.resolve(request.caller.cwd, card.summaryPage) : card.summaryPage; if (card.links) { const files = card.links.map(link => ('path' in link && request.caller.cwd && !path.isAbsolute(link.path) ? { ...link, path: path.resolve(request.caller.cwd, link.path) } : link)); request.links = [...request.links ?? [], ...files].slice(0, NOTIFY_LINKS_MAX); diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index bcfd29218..6822242ec 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -143,6 +143,9 @@ export interface WorkerQuestion { questions?: NotifySubQuestion[]; /** Set when Pane tidied its question by rule: the question and context as sent. */ asSent?: NotifyItem['asSent']; + /** Its own Summary page, and its status file (read for where it stands). */ + summaryPage?: string; + statusFile?: string; } export interface NotifyQueueEnvironment { @@ -489,6 +492,8 @@ export class NotifyQueue extends EventEmitter { if (question.links?.length) item.links = question.links; if (question.context) item.context = question.context; if (question.asSent) item.asSent = question.asSent; + if (question.summaryPage) item.summaryPage = question.summaryPage; + if (question.statusFile) item.statusFile = question.statusFile; if (question.questions?.length) item.questions = question.questions; else if (question.choices?.length) item.choices = question.choices; item.connected = question.connected ?? connectedOf(item, this.machine()); diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index 19ae68c03..cb3e1491e 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -258,6 +258,7 @@ export class NotifyService { choices: card.choices, questions: card.questions, asSent: card.asSent, + ...summaryFiles(entry.report?.summaryPath, card.summaryPage), }); } } @@ -761,3 +762,15 @@ function notifySettings(config: AppConfig): NotifySettings { function appearanceKey(config: AppConfig): string { return JSON.stringify([config.appearanceMode, config.theme, config.systemLightTheme, config.systemDarkTheme, config.highContrast]); } + +/** + * A worker card's Summary page and status file (P4): an .html passed as --summary-file is its + * Summary; any other file is its status file, whose needs_you may name a Summary page beside it. + */ +export function summaryFiles(summaryPath: string | undefined, needsYouPage: string | undefined): { summaryPage?: string; statusFile?: string } { + if (summaryPath && /\.html?$/iu.test(summaryPath)) return { summaryPage: summaryPath }; + const files: { summaryPage?: string; statusFile?: string } = {}; + if (summaryPath) files.statusFile = summaryPath; + if (needsYouPage) files.summaryPage = path.isAbsolute(needsYouPage) || !summaryPath ? needsYouPage : path.resolve(path.dirname(summaryPath), needsYouPage); + return files; +} diff --git a/main/src/services/notify/summaryPage.test.ts b/main/src/services/notify/summaryPage.test.ts new file mode 100644 index 000000000..8283eb7f9 --- /dev/null +++ b/main/src/services/notify/summaryPage.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { NOTIFY_SUMMARY_CSP, type NotifyItem } from '../../../../shared/types/notify'; +import { summaryPage } from './summaryPage'; +import { summaryFiles } from './notifyService'; + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pane-summary-')); +const statusFile = path.join(dir, 'status.json'); +fs.writeFileSync(statusFile, JSON.stringify({ state: 'blocked', step: 'P-16 built, make verify re-running', checks: { passed: ['unit', 'lint'], failed: [], undetermined: ['staging QA'] } })); + +const q80: NotifyItem = { + id: 'q80', ui: 'options', state: 'waiting', createdAt: Date.UTC(2026, 10, 11, 15, 15), + title: 'Q-80: Sign in to gcloud so staging can continue', + context: 'gcloud’s sign-in expired; the staging schema change and every QA journey wait on it.', + choices: [{ label: 'Done, signed in', detail: 'Applies the staging schema, then QA on staging.', recommended: true }, { label: 'Skip staging for now' }], + links: [{ label: 'wire-contract.md', path: '/work/p16/wire-contract.md' }, { label: 'PR #1359', url: 'https://github.com/o/r/pull/1359' }], + source: { name: 'custom-pricing-plans', agent: 'Claude', session: { id: 's', name: 'Subscriptions', paneId: 'p' } }, + statusFile, +}; + +describe("an approval's Summary page", () => { + it("is Pane's template when no page was sent: subtitle first, each option and its consequence, where it stands, references with real paths", () => { + const page = summaryPage(q80); + expect(page.kind).toBe('template'); + const html = page.kind === 'template' ? page.html : ''; + const text = html.replace(/
+

${escape(item.title)}${stateLabel(item)}

+

${meta}

+${item.context ? `

${escape(item.context)}

` : ''} +${choiceSections ? `

${questions.length > 1 ? 'Your choices' : 'Your choice'}

${choiceSections}` : ''} +${stands ? `

Where it stands. ${escape(stands)}

` : ''} +${references} +
Written by Pane from ${source} (no Summary was sent).
+
`); +} + +/** The asker's own page, read with the policy added, or why it can't be. */ +function askerPage(file: string): NotifySummaryPage { + let stat: fs.Stats; + try { + stat = fs.statSync(file); + } catch { + return { kind: 'missing', path: file, message: `The Summary page ${path.basename(file)} is gone: there is no file at ${file} any more, so Pane can't show it. Its worktree may have been removed.` }; + } + if (!stat.isFile()) return { kind: 'missing', path: file, message: `${file} is not a file, so Pane can't show it as the Summary.` }; + if (stat.size > PAGE_LIMIT_BYTES) return { kind: 'missing', path: file, message: `${path.basename(file)} is larger than 4 MiB, too large to show here. Open it from References instead.` }; + return { kind: 'page', path: file, html: withPolicy(fs.readFileSync(file, 'utf8')) }; +} + +/** The approval's Summary: the asker's page when it sent one, else Pane's template. */ +export function summaryPage(item: NotifyItem, theme: SummaryTheme = 'light'): NotifySummaryPage { + if (item.summaryPage) { + const page = askerPage(item.summaryPage); + // A page that's gone falls back to the template, which says so in its footer's place. + if (page.kind !== 'missing') return page; + return { kind: 'template', html: summaryTemplate(item, theme).replace('(no Summary was sent)', `(the Summary page it sent, ${escape(path.basename(item.summaryPage))}, is gone)`) }; + } + return { kind: 'template', html: summaryTemplate(item, theme) }; +} diff --git a/main/src/services/notify/workerContext.ts b/main/src/services/notify/workerContext.ts index 100514469..fe7a256ac 100644 --- a/main/src/services/notify/workerContext.ts +++ b/main/src/services/notify/workerContext.ts @@ -21,6 +21,7 @@ const needsYouSchema = boundary.object({ why: boundary.optional(boundary.string), questions: boundary.optional(boundary.array(questionSchema)), files: boundary.optional(boundary.array(boundary.string)), + summary: boundary.optional(boundary.string), }); type NeedsYou = NonNullable>; @@ -32,6 +33,8 @@ export interface StatusCard { /** Several questions, each with its own buttons, when every one has options to press. */ questions?: NotifySubQuestion[]; links?: NotifyLink[]; + /** The asker's own Summary page (`summary` in needs_you), as written: maybe relative. */ + summaryPage?: string; } function cut(text: string): string { @@ -169,6 +172,7 @@ function cardOf(needsYou: NeedsYou, title: string): StatusCard { if (parts.length) card.context = cut(parts.join('\n\n')); const links = (needsYou.files ?? []).map(linkOf).filter((link): link is NotifyLink => link !== undefined).slice(0, NOTIFY_LINKS_MAX); if (links.length) card.links = links; + if (needsYou.summary?.trim() && /\.html?(#.*)?$/iu.test(needsYou.summary.trim())) card.summaryPage = needsYou.summary.trim(); return card; } @@ -190,7 +194,7 @@ export function statusCard(text: string, title: string): StatusCard | undefined const needsYou = needsYouOf(json, body); if (needsYou) { const card = cardOf(needsYou, title); - if (card.context || card.choices || card.questions || card.links) return card; + if (card.context || card.choices || card.questions || card.links || card.summaryPage) return card; } const line = plainLine(json, body); return line ? { context: cut(line) } : {}; @@ -223,5 +227,5 @@ export function workerCard(summary: string | undefined, question: string, option return { ...card, title: question, warnings: [] }; } const { title, context, choices, questions, links } = admitted.card; - return { title, context, choices, questions, links, asSent: admitted.asSent, warnings: admitted.warnings }; + return { title, context, choices, questions, links, summaryPage: card.summaryPage, asSent: admitted.asSent, warnings: admitted.warnings }; } diff --git a/shared/remoteClient/readChannels.ts b/shared/remoteClient/readChannels.ts index 7d5d09477..5c2b0b638 100644 --- a/shared/remoteClient/readChannels.ts +++ b/shared/remoteClient/readChannels.ts @@ -12,6 +12,8 @@ export const REMOTE_READ_CHANNELS = new Set([ // Autopilot's state and its documents; nothing on the host changes. 'autopilot:snapshot', 'autopilot:read-doc', + // An approval's Summary page, read or written from its card; nothing on the host changes. + 'notify:summary-page', 'runpane:update:progress', 'config:get', 'config:get-session-preferences', diff --git a/shared/types/machineConnections.ts b/shared/types/machineConnections.ts index 893f4ee19..9bc7bb748 100644 --- a/shared/types/machineConnections.ts +++ b/shared/types/machineConnections.ts @@ -30,6 +30,7 @@ export const MACHINE_READ_CHANNELS = [ 'permission:getPending', 'notify:remote-snapshot', 'autopilot:read-doc', + 'notify:summary-page', 'panels:agent-statuses', 'projects:get-all', 'projects:list-branches', diff --git a/shared/types/notify.ts b/shared/types/notify.ts index ca63fdb34..8b8e9f97e 100644 --- a/shared/types/notify.ts +++ b/shared/types/notify.ts @@ -95,6 +95,13 @@ export interface NotifyQuestion { * exactly as they were sent, for Show as sent. The card reads "Tidied by Pane". */ asSent?: { title: string; context?: string }; + /** + * Set by the host: the asker's own Summary page (an .html passed as a worker's --summary-file, + * or `summary` in its needs_you). Absent: Pane writes the Summary from the card (P4). + */ + summaryPage?: string; + /** Set by the host for a worker's card: its status file, read for "where it stands" on the Summary. */ + statusFile?: string; /** * Set by the chat tab bridge for a built-in question that takes only its own choices: typed text * can't answer it and nobody could read it as a reply, so it has no reply box. @@ -599,3 +606,18 @@ export interface NotifyWindowSize { width: number; height: number; } + +/** + * `notify:summary-page [{ theme }]`: an approval's Summary as one HTML page, the same on the + * Mac, from another machine and on the phone. `page` is the asker's own page; `template` is the + * one Pane writes from the card and the worker's status file. Either renders with scripts and + * network off: the html carries a Content-Security-Policy that allows neither, and every viewer + * also turns scripts off. `missing` says why there is no page (the file was removed, say). + */ +export type NotifySummaryPage = + | { kind: 'page'; path: string; html: string } + | { kind: 'template'; html: string } + | { kind: 'missing'; path?: string; message: string }; + +/** The policy every Summary page is served with: no scripts, no network, inline styles and data: images only. */ +export const NOTIFY_SUMMARY_CSP = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src data:; media-src data:; form-action 'none'; base-uri 'none'"; From 77aea56229614a6684ce34eca758189da59935c6 Mon Sep 17 00:00:00 2001 From: Tyler Brown Date: Wed, 11 Nov 2026 17:43:34 -0800 Subject: [PATCH 05/13] Make Pane's approvals text the one rule for asking in Pane, with a test that fails on drift (P1) --- AGENTS.md | 2 +- contracts/runpane/contract.json | 5 +- docs/CHAT_TABS.md | 4 +- docs/PANE_NOTIFY.md | 26 ++++- docs/RUNPANE_CLI_CONTRACT.md | 2 +- docs/SESSIONS.md | 15 ++- main/src/services/approvalsRules.test.ts | 101 ++++++++++++++++++ main/src/services/approvalsRules.ts | 41 +++++++ .../notify/notifyInstructions.test.ts | 2 +- main/src/services/paneChatBundle.test.ts | 4 +- .../skills/orchestrate-sessions/SKILL.md | 2 + .../orchestrate-sessions/references/relay.md | 36 +++++++ .../skills/pane-orchestration/SKILL.md | 39 +++++-- main/src/services/paneHomeSkill.test.ts | 4 +- main/src/services/paneHomeSkill.ts | 30 +++--- main/src/services/sessionWorkspace.test.ts | 3 +- main/src/services/sessionWorkspace.ts | 4 +- main/src/services/skillCacheManager.test.ts | 2 +- main/src/services/skillCacheManager.ts | 20 ++-- .../src/runpane/generated_contract.py | 2 +- packages/runpane/src/generated/contract.ts | 5 +- shared/types/generatedRunpaneContract.ts | 5 +- 22 files changed, 286 insertions(+), 68 deletions(-) create mode 100644 main/src/services/approvalsRules.test.ts create mode 100644 main/src/services/approvalsRules.ts create mode 100644 main/src/services/paneChatBundle/skills/orchestrate-sessions/references/relay.md diff --git a/AGENTS.md b/AGENTS.md index a44da74f7..501d49362 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,7 +77,7 @@ This repository is used with [Pane](https://runpane.com). Drive it with the CLI 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane ` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane. -When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question ""`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets. +When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question ""`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets. CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`. diff --git a/contracts/runpane/contract.json b/contracts/runpane/contract.json index 14dd13bea..29231680c 100644 --- a/contracts/runpane/contract.json +++ b/contracts/runpane/contract.json @@ -23621,7 +23621,7 @@ "", "1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane ` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.", "", - "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"\"`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", + "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"\"`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", "", "CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.", "", @@ -23629,11 +23629,12 @@ ], "notifyGuidance": { "rules": [ + "Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.", "In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \"\"`, and the answer reaches its tab as `answer: Q-n → (by you)`; `closed: Q-n (by you)` and `correction: Q-n → (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.", "Ask only what you can't find out yourself.", "Put related questions in one card.", "Send `info` rarely, only for what the person must know now.", - "Write the card as its Summary: a title of about 80 characters; context saying why it matters; each choice saying what it does, with one recommended; links to every file, page or approval it mentions.", + "Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.", "A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took answer`. If it asks, run `--took question`, find out, then `runpane notify --update --asked \"\" --what-changed \"\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\"was\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.", "For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`." ], diff --git a/docs/CHAT_TABS.md b/docs/CHAT_TABS.md index 762232ccb..fa919f289 100644 --- a/docs/CHAT_TABS.md +++ b/docs/CHAT_TABS.md @@ -177,7 +177,9 @@ says, and after running the chat tab suites against it. plan itself. - **Questions** (ACP form elicitation, such as Claude's AskUserQuestion) dock as a form. Pane declares `elicitation.form`; without it Claude turns - AskUserQuestion off. + AskUserQuestion off. Agents use it only for a quick question in this chat; + anything else goes to an approval card + ([PANE_NOTIFY.md](PANE_NOTIFY.md#what-an-agent-can-send), "Which card"). - The composer is the input over one row of controls: attach image, the permission mode chip, then one menu showing model and effort together ("Opus 5.5 · Medium"; a trailing "(recommended)" and an effort of "Default" diff --git a/docs/PANE_NOTIFY.md b/docs/PANE_NOTIFY.md index a9b74251e..0c286f9e8 100644 --- a/docs/PANE_NOTIFY.md +++ b/docs/PANE_NOTIFY.md @@ -35,10 +35,28 @@ for another approval (its id or Q-n). Older Panes ignore them. In Approvals, clicking a chip opens it as a tab beside the card: a file in an editor tab, a page in a browser tab, another approval in place, with **Back**. -Pane builds each approval's **Summary** from these fields (D-225), so agents -write them to read on their own: a title of about 80 characters, `context` as -why it matters, each choice with what it does (`detail`) and one -`recommended`, and links to every reference: files, PRs and other approvals. +Pane builds each approval's **Summary** from these fields (D-225), unless the +asker linked its own `.html` Summary page (a worker's `--summary-file`, or +`summary` in its `needs_you`). Agents write a card to read on its own: a +one-line title of about 80 to 100 characters, `context` of one or two sentences +on why it matters (up to 240 characters), 2 to 4 choices each with what it does +(`detail`) and exactly one `recommended`, and links to every reference: files, +PRs and other approvals. Every way of asking goes through one check +(`main/src/services/notify/cardCheck.ts`): Pane refuses a title over 160 +characters or with a line break, and two recommended options in one question, +and returns a warning for each softer miss, which `runpane notify` and +`runpane report` print. An old paragraph card (the whole question in its title, +with no buttons) is first split by fixed rules into a title, options and +links (`cardTidy.ts`); the card reads "Tidied by Pane" and **Show as sent** +shows what the agent sent. + +**Which card.** Pane's approvals text (`main/src/services/approvalsRules.ts`, +"Asking the person in Pane") is the one rule every Pane text carries: Claude's +own card (AskUserQuestion) only for a quick question that answers the person's +latest message in the chat they are typing in and only steers that turn; +everything else, including any worker's question in a Session, is an approval +card. Codex and other agents without a built-in card always use an approval. +`main/src/services/approvalsRules.test.ts` fails when a copy drifts. When Approvals is off or Pane isn't running, the result is `{unavailable, next}` ("Approvals are off in Pane.") and the agent asks in its diff --git a/docs/RUNPANE_CLI_CONTRACT.md b/docs/RUNPANE_CLI_CONTRACT.md index a1b59a625..ecd2501f7 100644 --- a/docs/RUNPANE_CLI_CONTRACT.md +++ b/docs/RUNPANE_CLI_CONTRACT.md @@ -622,7 +622,7 @@ This repository is used with [Pane](https://runpane.com). Drive it with the CLI 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane ` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane. -When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question ""`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets. +When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question ""`. Its orchestrator relays that card with `runpane notify --relays ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack --decision `. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets. CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`. diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index beebe772e..5734049bf 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -1420,20 +1420,19 @@ default), a Session has one voice to the person: its orchestrator. person") tell the orchestrator that only it asks the person; to keep a running list and keep the work moving; when the person asks about one thing while other work is open, to remind them and offer the next step; at - the end of a turn, to have an approval waiting or be done; and to ask - through Approvals with context, in the Summary's format (D-225): a title of - about 80 characters, why it matters, each option with what it does and the - recommended one first, and links to every reference, other approvals - included (`{label, approval}`); and to read a typed reply as an answer or a - question. These are instructions; Pane doesn't check turns. + the end of a turn, to have an approval waiting or be done. All three carry + Pane's approvals text, "Asking the person in Pane" + (`main/src/services/approvalsRules.ts`, also in the `pane` home skill): which + card to use, what a card holds and how to read a reply + ([PANE_NOTIFY.md](PANE_NOTIFY.md#what-an-agent-can-send)). These are + instructions; Pane doesn't check turns, only cards. - **The orchestrator relays and posts.** The same three carry the posting rule: keep every item that waits on the person (a worker's needs_you or blocked question, a plan or design approval, a PR ready to merge, a next step only they can take) posted to Approvals exactly once, titled with its Q-n and with the worker's files and PR as `--links`. Post each one with `runpane notify ... --json` as a background command (in Claude Code, Bash with `run_in_background`), not the MCP `notify` tool, - which returns `waiting` after a while, and not a blocking - `AskUserQuestion`. A worker's question already has a card, so the orchestrator + which returns `waiting` after a while, and never `AskUserQuestion`. A worker's question already has a card, so the orchestrator relays into it: `runpane notify --relays ...`, with the id from `runpane inbox`, gives that card its choices, context and links and rings it, and the call waits on it as on any notify. The answer goes straight to the worker's diff --git a/main/src/services/approvalsRules.test.ts b/main/src/services/approvalsRules.test.ts new file mode 100644 index 000000000..5e2615829 --- /dev/null +++ b/main/src/services/approvalsRules.test.ts @@ -0,0 +1,101 @@ +import fs from 'fs/promises'; +import os from 'os'; +import path from 'path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { RUNPANE_CONTRACT } from '../../../shared/types/generatedRunpaneContract'; +import { APPROVALS_RULES, APPROVALS_RULES_POINTER } from './approvalsRules'; +import { buildPaneHomeSkill } from './paneHomeSkill'; +import { prepareSessionWorkspace } from './sessionWorkspace'; +import { SkillCacheManager } from './skillCacheManager'; + +const bundle = path.join(__dirname, 'paneChatBundle', 'skills'); +const read = (file: string) => fs.readFile(file, 'utf8'); + +/** Wording that would send a worker's or a relayed question through Claude's own card. */ +const ASK_USER_QUESTION_INSTRUCTION = /\b(use|call|through|with|via|put [^.]* through)\s+(the\s+)?`?AskUserQuestion`?|harness question tool/i; + +function expectNoAskUserQuestionInstruction(text: string, where: string): void { + for (const sentence of text.replace(/\s+/g, ' ').split(/(?<=[.;:])\s/)) { + if (!/AskUserQuestion|harness question tool/i.test(sentence)) continue; + // Naming it to forbid it ("never", "not", "don't") is fine; telling an agent to use it is not. + if (/\b(never|not|don't|no)\b/i.test(sentence)) continue; + expect(sentence, where).not.toMatch(ASK_USER_QUESTION_INSTRUCTION); + } +} + +describe('Pane approvals rules: one text everywhere an agent learns to ask (D-342.1, D-347)', () => { + const previousPaneDir = process.env.PANE_DIR; + let root: string; + + beforeEach(async () => { + root = await fs.mkdtemp(path.join(os.tmpdir(), 'pane-approvals-rules-')); + process.env.PANE_DIR = root; + }); + + afterEach(async () => { + if (previousPaneDir === undefined) delete process.env.PANE_DIR; + else process.env.PANE_DIR = previousPaneDir; + await fs.rm(root, { recursive: true, force: true }); + }); + + it('carries the whole section in the generated pane-orchestrator skill, all four copies', async () => { + const manager = new SkillCacheManager(); + await manager.ensurePaneChatGuide(); + for (const file of [manager.paneChatOrchestratorSkillPath, manager.codexPaneOrchestratorSkillPath, manager.claudePaneOrchestratorSkillPath, manager.cursorPaneOrchestratorRulePath]) { + const text = await read(file); + expect(text, file).toContain(APPROVALS_RULES); + expectNoAskUserQuestionInstruction(text.replace(APPROVALS_RULES, ''), file); + } + }); + + it('keeps the bundled pane-orchestration guide\'s marked block the same as the source', async () => { + const text = await read(path.join(bundle, 'pane-orchestration', 'SKILL.md')); + const block = /\n([\s\S]*?)\n/.exec(text)?.[1]; + expect(block, 'Copy APPROVALS_RULES from approvalsRules.ts between the approvals-rules markers').toBe(APPROVALS_RULES); + expectNoAskUserQuestionInstruction(text.replace(APPROVALS_RULES, ''), 'pane-orchestration/SKILL.md'); + }); + + it('carries the section in each Session\'s AGENTS.md', async () => { + const cwd = prepareSessionWorkspace('approvals-rules'); + const agents = await read(path.join(cwd, 'AGENTS.md')); + expect(agents).toContain(APPROVALS_RULES); + expectNoAskUserQuestionInstruction(agents.replace(APPROVALS_RULES, ''), 'Session AGENTS.md'); + }); + + it('carries the section in the pane home skill, under the name the pointer gives', () => { + const skill = buildPaneHomeSkill(); + expect(skill).toContain(APPROVALS_RULES); + expect(APPROVALS_RULES.startsWith('## Asking the person in Pane\n')).toBe(true); + expect(APPROVALS_RULES_POINTER).toContain('"Asking the person in Pane"'); + expectNoAskUserQuestionInstruction(skill.replace(APPROVALS_RULES, ''), 'pane home skill'); + }); + + it('points the bundled orchestrate-sessions skill at the section and never relays through Claude\'s card', async () => { + const skill = await read(path.join(bundle, 'orchestrate-sessions', 'SKILL.md')); + const relay = await read(path.join(bundle, 'orchestrate-sessions', 'references', 'relay.md')); + expect(skill).toContain('[references/relay.md](references/relay.md)'); + expect(skill).toContain('"Asking the person in Pane"'); + expect(relay).toContain('"Asking the person in Pane"'); + expect(relay).toContain('## A check, not a pipe'); + expect(relay).toContain('clarify: Q-n → '); + expectNoAskUserQuestionInstruction(skill, 'orchestrate-sessions/SKILL.md'); + expectNoAskUserQuestionInstruction(relay, 'orchestrate-sessions/references/relay.md'); + }); + + it('gives the same which-card rule in the notify guidance and the managed AGENTS.md block', () => { + const { notifyGuidance, managedBlock } = RUNPANE_CONTRACT.agentContext; + const guidance = [...notifyGuidance.rules, notifyGuidance.cardRule, notifyGuidance.results].join('\n'); + const block = managedBlock.join('\n'); + for (const [where, text] of [['notifyGuidance', guidance], ['managedBlock', block]] as const) { + expect(text, where).toContain("Claude's own card (AskUserQuestion)"); + expect(text, where).toContain('approval card'); + expectNoAskUserQuestionInstruction(text, where); + } + }); + + it('catches a text that tells an agent to use AskUserQuestion', () => { + expect(() => expectNoAskUserQuestionInstruction('Relay the question with AskUserQuestion once the files are open.', 'sample')).toThrow(); + expect(() => expectNoAskUserQuestionInstruction('On Claude, put the questions through the harness question tool.', 'sample')).toThrow(); + expect(() => expectNoAskUserQuestionInstruction("Don't block your turn on AskUserQuestion.", 'sample')).not.toThrow(); + }); +}); diff --git a/main/src/services/approvalsRules.ts b/main/src/services/approvalsRules.ts new file mode 100644 index 000000000..1b3999704 --- /dev/null +++ b/main/src/services/approvalsRules.ts @@ -0,0 +1,41 @@ +/** + * How an agent asks the person inside Pane: the one source (D-342.1, D-347). Pane's generated + * pane-orchestrator skill, the bundled pane-orchestration skill, each Session's AGENTS.md, the + * `pane` home skill and the bundled orchestrate-sessions skill all carry this text or point to it, + * and approvalsRules.test.ts fails when one of them drifts. Agent Farm points to the pane skill. + */ + +/** Which card to use: Claude's own card for a quick question in the chat the person is in, an approval for everything else. */ +export const WHICH_CARD_RULE = [ + '### Which card', + '', + "- **Claude's own card (AskUserQuestion)** only when all of these hold: you are answering the person's latest message in the chat they are typing in; the answer only steers this turn (nothing recorded as a decision, nothing for another agent, nothing irreversible or outward-facing); and the question stands on its own, without context, links or a Summary. Examples: \"Update the snapshot too, or leave it?\", \"Which of these two files did you mean?\"", + '- **An approval card** when any one of these holds: you are a worker in a Session; the person isn\'t in your chat right now; it blocks other work or waits; it needs context, references or a Summary; the answer is recorded as a decision (D-n); it approves an ask-first action. Examples: a plan approval, a sign-in the person must do, a migration.', + '- Unsure? Use an approval card. Codex and other agents without a built-in card always use an approval.', + '- How to post one: a worker runs `runpane report --state blocked --question "" --summary-file <its status file>` (its `needs_you` gives the questions, options and files); a Session orchestrator runs `runpane notify` (or `--relays <item>` for a worker\'s question), never AskUserQuestion; any other agent calls the `notify` MCP tool or `runpane notify`.', +].join('\n'); + +/** What a good approval card holds. Pane refuses the two clear mistakes and warns on the rest. */ +export const CARD_CONTRACT = [ + '### The card', + '', + '- **Title**: what you need, about 80 to 100 characters on one line. Pane refuses a title over 160 characters or with a line break.', + '- **Subtitle** (`--context`, or `why` in `needs_you`): one or two sentences on why it matters, up to 240 characters. It shows under the title everywhere and opens the Summary.', + '- **Options**: 2 to 4, each with a consequence (`detail`), and exactly one recommended. Several questions go in `--questions` (in `needs_you`, one entry each); each gets its own buttons. Pane refuses two recommended options in one question.', + '- **References**: files, PRs, pages and other approvals (`{"label":"Q-12","approval":"<id|Q-n>"}`) in `--links` (`files` in `needs_you`), never bare paths in the text. When there is more to read, give the card a Summary page: an `.html` file passed as a worker\'s `--summary-file`, or a `summary` path in `needs_you`. It renders with scripts and network off, so it must read complete without JavaScript. Without one, Pane writes the Summary from the card.', + '- Pane prints a warning for each softer miss (a long title, no subtitle, no recommendation, more than 4 options, options without a consequence, unlinked paths). Fix them on the next card.', +].join('\n'); + +/** After the person replies: say how you took it, and update the card when it was a question. */ +export const REPLY_RULE = [ + '### Replies', + '', + '- A typed reply may be an answer or a question. Whoever reads it runs `runpane notify --took <id|Q-n> answer` or `--took <id|Q-n> question`.', + '- For a question: find out, then `runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links`. The card shows "Updated · ready again" and waits for the answer. Don\'t post a second card.', +].join('\n'); + +/** The whole section, as every Pane text that teaches asking carries it. */ +export const APPROVALS_RULES = ['## Asking the person in Pane', '', WHICH_CARD_RULE, '', CARD_CONTRACT, '', REPLY_RULE].join('\n'); + +/** The one-line pointer a shorter text carries instead of the whole section. */ +export const APPROVALS_RULES_POINTER = 'Ask the person the way the `pane` skill\'s "Asking the person in Pane" section says: Claude\'s own card (AskUserQuestion) only for a quick question in the chat they are typing in, an approval card for everything else.'; diff --git a/main/src/services/notify/notifyInstructions.test.ts b/main/src/services/notify/notifyInstructions.test.ts index f4af53081..2e9e8ed67 100644 --- a/main/src/services/notify/notifyInstructions.test.ts +++ b/main/src/services/notify/notifyInstructions.test.ts @@ -59,7 +59,7 @@ describe('Pane Notify instructions in the agents\' user-level files', () => { expect(written).toContain('- A `reply:` line (or a `{reply, replyId}` result) is the person\'s typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`.'); expect(written).toContain('then `runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>"`'); expect(written).toContain('A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.\r\n'); - expect(written).toContain('- Write the card as its Summary: a title of about 80 characters; context saying why it matters;'); + expect(written).toContain('- Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters;'); expect(written.endsWith(`${END}\r\n`)).toBe(true); // Codex's folder had no AGENTS.md, so Pane created one holding only the block. expect(text(codexFile).startsWith(BEGIN)).toBe(true); diff --git a/main/src/services/paneChatBundle.test.ts b/main/src/services/paneChatBundle.test.ts index 4992e5e51..2beedec76 100644 --- a/main/src/services/paneChatBundle.test.ts +++ b/main/src/services/paneChatBundle.test.ts @@ -83,7 +83,7 @@ describe('Pane Chat skill bundle', () => { expect(guide).toContain('Approvals come from you, not from the agents in'); expect(guide).toContain('either have an approval waiting for the person'); expect(guide).toContain('or be done and say so.'); - expect(guide).toContain('pass the related files, PRs and pages as `--links`.'); + expect(guide).toContain('Ask the way "Asking the person in Pane" above says.'); expect(guide).not.toMatch(/never (be )?stalled|Nothing left to do in this Session/i); expect(guide).toContain('Post or relay with the CLI as a background command, one per item, so you stay free to talk'); expect(guide).toContain('runpane notify --ui options --title "Q-7: <the question>"'); @@ -96,7 +96,7 @@ describe('Pane Chat skill bundle', () => { expect(guide).toContain('`runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>"`'); expect(guide).toContain('Never post a second approval for it.'); expect(guide).toContain('`correction: Q-n → that was a question: "…" (by you)`'); - expect(guide).toContain('a title of about 80 characters'); + expect(guide).toContain('about 80 to 100 characters on one line'); expect(guide).toContain('{"label":"Q-12","approval":"<id|Q-n>"}'); expect(guide).toContain('stop its background command (TaskStop in Claude Code)'); expect(guide).toContain('Approvals survive a restart'); diff --git a/main/src/services/paneChatBundle/skills/orchestrate-sessions/SKILL.md b/main/src/services/paneChatBundle/skills/orchestrate-sessions/SKILL.md index 089f919f8..476999857 100644 --- a/main/src/services/paneChatBundle/skills/orchestrate-sessions/SKILL.md +++ b/main/src/services/paneChatBundle/skills/orchestrate-sessions/SKILL.md @@ -109,6 +109,8 @@ A quiet worker or a long-running step is normal. On a reported failure, explicit Answer from an existing approved source when you can, and cite where. Leave routine in-scope technical decisions to the assigned worker. For a bounded, unresolved technical question that merits a second opinion, ask one fresh-context subagent. Send product and architecture changes, and ask-first actions, to the user. In Pane, while Approvals is on, post each one to Approvals as `pane-orchestration` says ("What waits on the person"), so you stay free to talk. +Workers raise questions as a `needs_you` block in their status file. Relay each one as [references/relay.md](references/relay.md) says: check it first (you are a check, not a pipe), then relay it the way the `pane` skill's "Asking the person in Pane" section says, always as an approval card. Record the answer as a D-n, or send `clarify: Q-n → …` when the person asks back, which keeps Q-n open. + ## Presenting decisions This is the canonical decision frame for Pane's skills; others link here diff --git a/main/src/services/paneChatBundle/skills/orchestrate-sessions/references/relay.md b/main/src/services/paneChatBundle/skills/orchestrate-sessions/references/relay.md new file mode 100644 index 000000000..39363f07d --- /dev/null +++ b/main/src/services/paneChatBundle/skills/orchestrate-sessions/references/relay.md @@ -0,0 +1,36 @@ +# Relay + +How a worker's `needs_you` (standing rules, Questions for the person) reaches the person, and how the answer goes back. One round at a time, newest blocking question first; batch `fyi` blocks into the map instead. + +## A check, not a pipe + +Before a worker's question goes to the person, check it: + +- Answer it yourself when an approved source settles it (the person's request, the plan, an earlier D-n), and say which. Only the rest goes to the person. +- Check its premises: is the assumed constraint real, is a simpler option missing, is each comparison true? When its options fall short of [Presenting decisions](../SKILL.md#presenting-decisions), send it back to the worker instead of filling the gaps with guesses. +- Every option has a consequence, one is recommended, and its files are listed. Keep the worker's options and wording otherwise. +- A product decision goes on its own, never inside a batch of technical items. + +## To the person + +In Pane, relay it into the worker's card the way the `pane` skill's "Asking the person in Pane" section and `pane-orchestration`'s "What waits on the person" say (`runpane notify --relays <item>`, its files as links). Never put a worker's question through Claude's own card (AskUserQuestion): a worker's question is always an approval card. + +On a host without Pane's approvals, put the round in the conversation, its files opened beside the person, or a `Context: <where the context lives>` line when it has none: + +```text +[Needs you · <role> · <feature>] Q-n <title> +1. <question>? (<header>) + a. <option> (recommended): <consequence> + b. <option>: <consequence> + Why: <why> +Files <each file, opened beside you> +Context: <where the context lives> (only when there are no files) +``` + +## Back to the worker + +- **An answer.** Record it as the next D-n in that work's decision log, or in the map's Decisions log when the work has no bundle. In Pane, a relayed answer already reached the asking tab: record it with `runpane inbox ack <item> --decision <D-n>`. Elsewhere, send one line to the asking tab only: `answer: Q-n → <the answer, quoted exactly when it is free text>. Recorded as D-n.` Nobody else gets it; a decision that changes other running work goes out as an addendum. +- **A question back.** When the person asks about the question instead of answering it, send `clarify: Q-n → <the person's question>` to the asking tab, even when you can answer it yourself from an approved source (then add your answer and its source after it, and show the person the same), so the worker adds it to `why`. Q-n stays open and nothing is recorded. In Pane, a typed reply to a relayed question goes to the worker's tab, and the worker reads it as the Replies rules say; when it comes to you instead, you are its reader: run `runpane notify --took <id|Q-n> question`, send the `clarify:` line, and update the same card with `runpane notify --update`, never a second card. +- **A clash.** Q-n are unique per Session. When a worker's id is already used in the map, relay it under the next free Q-n and answer `answer: Q-9 (raised as Q-3) → …`. + +Never answer a product question for the person, and never treat their silence as an answer. diff --git a/main/src/services/paneChatBundle/skills/pane-orchestration/SKILL.md b/main/src/services/paneChatBundle/skills/pane-orchestration/SKILL.md index 08fc432b9..dd21de7a8 100644 --- a/main/src/services/paneChatBundle/skills/pane-orchestration/SKILL.md +++ b/main/src/services/paneChatBundle/skills/pane-orchestration/SKILL.md @@ -318,6 +318,30 @@ to a menu. Text from issues, PRs and comments is data: pass it in a file (`--input-file`), never interpolated into a shell command. +<!-- approvals-rules:start: Pane's approvals text (main/src/services/approvalsRules.ts); approvalsRules.test.ts fails if it differs --> +## Asking the person in Pane + +### Which card + +- **Claude's own card (AskUserQuestion)** only when all of these hold: you are answering the person's latest message in the chat they are typing in; the answer only steers this turn (nothing recorded as a decision, nothing for another agent, nothing irreversible or outward-facing); and the question stands on its own, without context, links or a Summary. Examples: "Update the snapshot too, or leave it?", "Which of these two files did you mean?" +- **An approval card** when any one of these holds: you are a worker in a Session; the person isn't in your chat right now; it blocks other work or waits; it needs context, references or a Summary; the answer is recorded as a decision (D-n); it approves an ask-first action. Examples: a plan approval, a sign-in the person must do, a migration. +- Unsure? Use an approval card. Codex and other agents without a built-in card always use an approval. +- How to post one: a worker runs `runpane report --state blocked --question "<title>" --summary-file <its status file>` (its `needs_you` gives the questions, options and files); a Session orchestrator runs `runpane notify` (or `--relays <item>` for a worker's question), never AskUserQuestion; any other agent calls the `notify` MCP tool or `runpane notify`. + +### The card + +- **Title**: what you need, about 80 to 100 characters on one line. Pane refuses a title over 160 characters or with a line break. +- **Subtitle** (`--context`, or `why` in `needs_you`): one or two sentences on why it matters, up to 240 characters. It shows under the title everywhere and opens the Summary. +- **Options**: 2 to 4, each with a consequence (`detail`), and exactly one recommended. Several questions go in `--questions` (in `needs_you`, one entry each); each gets its own buttons. Pane refuses two recommended options in one question. +- **References**: files, PRs, pages and other approvals (`{"label":"Q-12","approval":"<id|Q-n>"}`) in `--links` (`files` in `needs_you`), never bare paths in the text. When there is more to read, give the card a Summary page: an `.html` file passed as a worker's `--summary-file`, or a `summary` path in `needs_you`. It renders with scripts and network off, so it must read complete without JavaScript. Without one, Pane writes the Summary from the card. +- Pane prints a warning for each softer miss (a long title, no subtitle, no recommendation, more than 4 options, options without a consequence, unlinked paths). Fix them on the next card. + +### Replies + +- A typed reply may be an answer or a question. Whoever reads it runs `runpane notify --took <id|Q-n> answer` or `--took <id|Q-n> question`. +- For a question: find out, then `runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links`. The card shows "Updated · ready again" and waits for the answer. Don't post a second card. +<!-- approvals-rules:end --> + ## What waits on the person: Approvals You are in orchestrator mode: this Session's work moves through you. @@ -343,15 +367,9 @@ You are in orchestrator mode: this Session's work moves through you. - **At the end of a turn**, either have an approval waiting for the person in Approvals that moves the work on, or be done and say so. A turn that ends while your workers run, with your watch armed, is fine. -- **Ask through Approvals, always with context:** say in `--context` why it - needs them and what each option means, list the options with the one you - recommend first, and pass the related files, PRs and pages as `--links`. - Pane builds the approval's Summary from these fields, so: a title of about - 80 characters, context as why it matters, each choice with what it does - (`detail`) and one `recommended`, and links to every reference, other - approvals included (`{"label":"Q-12","approval":"<id|Q-n>"}`). Related - questions go in one options card with `--questions - '[{"title":"<question>","choices":[...]},...]'` instead of `--choices`. +- **Ask the way "Asking the person in Pane" above says.** As this + Session's orchestrator you post every approval card yourself, with + `runpane notify`, and relay a worker's question into its card. The rest of this section is how, while Approvals is on in Pane (its Settings switch, on by default). @@ -385,8 +403,7 @@ approval, a PR ready to merge, and a next step only they can take. ``` The CLI call waits until the person answers. The MCP `notify` tool returns - `waiting` after a while instead, so don't use it for this, and don't block - your turn on `AskUserQuestion`. + `waiting` after a while instead, so don't use it for this. - **When a relayed call returns, record the decision**: `runpane inbox ack <item> --decision <D-n>`. The card then reads "Recorded as D-n". An answer to your own item comes back as the call's result: record it diff --git a/main/src/services/paneHomeSkill.test.ts b/main/src/services/paneHomeSkill.test.ts index 43f53cb95..f321c0d75 100644 --- a/main/src/services/paneHomeSkill.test.ts +++ b/main/src/services/paneHomeSkill.test.ts @@ -82,7 +82,7 @@ describe('Pane home skill', () => { it('tells a Session worker to report blocked and an orchestrator to post in the background', () => { const skill = buildPaneHomeSkill().replace(/\s+/g, ' '); - expect(PANE_MANAGED_SKILL_MARKER).toBe('<!-- pane-managed-skill v3 -->'); + expect(PANE_MANAGED_SKILL_MARKER).toBe('<!-- pane-managed-skill v4 -->'); expect(skill).toContain("A worker in a Session** (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't"); expect(skill).toContain("doesn't call `notify`. Run `runpane report --state blocked --question \"<question>\"`"); expect(skill).toContain('A Session orchestrator** (`PANE_ORCHESTRATION_SESSION_ID` is set) keeps each item that waits on the person posted once, as a background `runpane notify'); @@ -96,7 +96,7 @@ describe('Pane home skill', () => { expect(skill).toContain('`runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>" --json`'); expect(skill).toContain('never post a second approval for it.'); expect(skill).toContain('`correction: Q-n → that was your answer: "…" (by you)` means it is recorded as the answer: act on it.'); - expect(skill).toContain('a title of about 80 characters'); + expect(skill).toContain('about 80 to 100 characters on one line'); expect(skill).toContain('{"label":"Q-12","approval":"<id|Q-n>"}'); }); diff --git a/main/src/services/paneHomeSkill.ts b/main/src/services/paneHomeSkill.ts index fad0e6b9a..6b6cd3388 100644 --- a/main/src/services/paneHomeSkill.ts +++ b/main/src/services/paneHomeSkill.ts @@ -8,6 +8,7 @@ import type { AppConfig } from '../types/config'; import { boundary, decodeBoundary } from '../../../shared/validation/boundaryDecoder'; import { getAppDirectory } from '../utils/appDirectory'; import { linuxToUNCPath } from '../utils/wslUtils'; +import { APPROVALS_RULES } from './approvalsRules'; /** * A user-level skill that teaches any agent in a Pane terminal how to reach @@ -17,7 +18,7 @@ import { linuxToUNCPath } from '../utils/wslUtils'; * Bump the version when the skill changes shape: a copy marked by an older Pane * is rewritten, and a copy marked by a newer Pane is left for that Pane. */ -const PANE_MANAGED_SKILL_VERSION = 3; +const PANE_MANAGED_SKILL_VERSION = 4; export const PANE_MANAGED_SKILL_MARKER = `<!-- pane-managed-skill v${PANE_MANAGED_SKILL_VERSION} -->`; const MANAGED_MARKER_PATTERN = /<!-- pane-managed-skill v(\d+) -->/; const SKILL_DIR_NAME = 'pane'; @@ -315,26 +316,23 @@ of it through the \`runpane\` CLI. - Workers end with \`runpane report --state <ready|blocked|failed|done> --summary-file <path>\` (\`--question\` when blocked, \`--pr\` and \`--head\` when there is a PR). -## Asking the person +${APPROVALS_RULES} -When you need a choice, an approval or a short answer, use the \`pane\` MCP -\`notify\` tool instead of asking in the terminal. If the \`pane\` MCP tools are -not loaded (some launchers start agents with a strict MCP config), run the same +### Posting an approval card + +Post it with the \`pane\` MCP \`notify\` tool. If the \`pane\` MCP tools are not +loaded (some launchers start agents with a strict MCP config), run the same call through the CLI: \`runpane notify --ui options --title "<question>" --context "<what it is for>" --choices '[{"label":"Ship it","detail":"<consequence>","recommended":true},{"label":"Wait"}]' --json\`. \`--ui\` is \`options\`, \`approve\` (\`--detail\`), \`ask\` (\`--suggestions\`) or \`info\`. On \`waiting\`, run \`runpane notify --resume <id> --json\`; on -\`unavailable\`, ask in the terminal. +\`unavailable\`, ask in the terminal. Related questions go in one options card: +\`--questions '[{"title":"<question>","choices":[...]},...]'\` (2 to 4, one +pick each) instead of \`--choices\`. -Write every approval so it reads on its own (Pane builds its Summary from -these fields): a title of about 80 characters; \`--context\` saying why it -matters; each choice with what it does (\`detail\`) and one \`recommended\`; and -\`--links\` to every reference: files, PRs, and other approvals -(\`{"label":"Q-12","approval":"<id|Q-n>"}\`). Related questions go in one -options card: \`--questions '[{"title":"<question>","choices":[...]},...]'\` -(2 to 4, one pick each) instead of \`--choices\`. +### Reading a reply -**When the person types a reply.** Picks alone come back as before: \`{answer}\`, or +Picks alone come back as before: \`{answer}\`, or \`answer: Q-n → <answer> (by you)\` in your tab. Anything typed comes back as a reply: the open call's result \`{reply, picks?, id, replyId, next}\` (pass its \`replyId\` to \`--took\` and \`--update\`: only your call was given it, so it works @@ -364,7 +362,7 @@ and you decide which: Only the reply's reader may run \`--took\` and \`--update\`: the asker, else the orchestrator that relayed a worker's question, else Autopilot. -Who asks depends on your role: +### Who asks, by role - **A worker in a Session** (\`PANE_SESSION_DIR\` is set and \`PANE_ORCHESTRATION_SESSION_ID\` isn't, or your brief names an orchestrator @@ -385,6 +383,8 @@ Who asks depends on your role: worker's question it relayed comes to it when the worker can't read it. - **Any other agent** calls \`notify\` as above. +### Secrets + For an API key, token or password, call the \`secrets_request\` tool (or \`runpane secrets request --project <project> --name <NAME> --title "<text>" --reason "<what it is for>"\`), never \`ask\`. Pane saves the value as a Pane secret and returns only saved or diff --git a/main/src/services/sessionWorkspace.test.ts b/main/src/services/sessionWorkspace.test.ts index 877625ef8..789321565 100644 --- a/main/src/services/sessionWorkspace.test.ts +++ b/main/src/services/sessionWorkspace.test.ts @@ -209,7 +209,8 @@ describe('Session workspace instructions', () => { expect(agents).toContain('run runpane notify --took <id|Q-n> answer and act on it'); expect(agents).toContain('runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>"'); expect(agents).toContain('never post a second approval for it'); - expect(agents).toContain('a title of about 80 characters'); + expect(agents).toContain('## Asking the person in Pane'); + expect(agents).toContain('about 80 to 100 characters on one line'); expect(agents).toContain('Stop the background call of an item that went stale.'); expect(agents).toContain('Approvals survive a restart'); expect(agents).not.toContain('Pane keeps no approvals'); diff --git a/main/src/services/sessionWorkspace.ts b/main/src/services/sessionWorkspace.ts index 9f6b68756..3e02cfa47 100644 --- a/main/src/services/sessionWorkspace.ts +++ b/main/src/services/sessionWorkspace.ts @@ -6,6 +6,7 @@ import { getAppDirectory } from '../utils/appDirectory'; import { DEFAULT_SESSION_PROFILE, PANE_CAPABILITY_CONTEXT } from '../../../shared/types/sessionProfile'; import type { OrchestrationSessionRecord } from '../../../shared/types/orchestrationSession'; import { keepSessionTidySection } from './sessionTidyRules'; +import { APPROVALS_RULES } from './approvalsRules'; const START = '<!-- pane-session-context:start -->'; const END = '<!-- pane-session-context:end -->'; @@ -198,7 +199,8 @@ At the start of each task that uses Pane, run runpane doctor --json --pane-dir " Never substitute plain git worktrees or built-in or background subagents for work delegated to a Pane. If runpane cannot reach Pane, stop and tell the user what failed. Read ${guide} before coordinating Panes for the first time in a conversation. Follow its pane-orchestration guide for delivery, completion waits, and recovery. ## Orchestrator mode -You are in orchestrator mode: this Session's work moves through you. Only you ask the person: approvals come from you, not from the agents in this Session's Panes, who talk to you through needs_you or runpane report --state blocked --question. Answer them yourself when the person's request, the plan or an earlier decision settles it, and say which; ask the person only about the rest. Keep things moving: keep a running list of what this Session has to do, and read it again when you start, resume or wake. When the person asks about one thing while other work is open, answer it, then remind them what else is open and offer the next step. At the end of a turn, either have an approval waiting for the person in Approvals that moves the work on, or be done and say so; a turn that ends while your workers run, with your watch armed, is fine. Ask through Approvals, always with context: a title of about 80 characters, why it needs them (--context), each option with what it does and the one you recommend first, and every related file, PR, page and other approval as --links ({"label":"Q-12","approval":"<id|Q-n>"} for an approval). Related questions go in one options card with --questions instead of --choices. Messages can also come from Pane's Autopilot: one that starts with [From the person, via Autopilot] carries the person's own words; one that starts with [From Autopilot] is Autopilot's own (a first brief for a Session it created, a check-in, a question about an item). Reply to whoever asked and say which ("To Autopilot:"). Decisions (plans, merges, anything the person must choose) still go to the person as approvals, whoever asked. +You are in orchestrator mode: this Session's work moves through you. Only you ask the person: approvals come from you, not from the agents in this Session's Panes, who talk to you through needs_you or runpane report --state blocked --question. Answer them yourself when the person's request, the plan or an earlier decision settles it, and say which; ask the person only about the rest. Keep things moving: keep a running list of what this Session has to do, and read it again when you start, resume or wake. When the person asks about one thing while other work is open, answer it, then remind them what else is open and offer the next step. At the end of a turn, either have an approval waiting for the person in Approvals that moves the work on, or be done and say so; a turn that ends while your workers run, with your watch armed, is fine. Ask the way the next section, Asking the person in Pane, says: you post every approval card yourself with runpane notify, and relay a worker's question into its card. Messages can also come from Pane's Autopilot: one that starts with [From the person, via Autopilot] carries the person's own words; one that starts with [From Autopilot] is Autopilot's own (a first brief for a Session it created, a check-in, a question about an item). Reply to whoever asked and say which ("To Autopilot:"). Decisions (plans, merges, anything the person must choose) still go to the person as approvals, whoever asked. +${APPROVALS_RULES} ## What waits on the person (while Approvals is on) Keep every item that waits on the person (a worker's needs_you or blocked question, a plan or design approval, a PR ready to merge, a next step only they can take) posted to Pane's Approvals, exactly once. Workers in this Session don't post; they report blocked to you. Post each item when it appears with runpane notify ... --json as a background command (in Claude Code, Bash with run_in_background), not the MCP notify tool or AskUserQuestion, so you stay free to talk; title it with its Q-n and pass the worker's files and PR as --links. A worker's blocked question already has a card: relay into it with runpane notify --relays <item> ... (the id from runpane inbox), which improves that card instead of adding one. The answer goes straight to the worker's tab (answer: Q-n → <answer> (by you)), so don't forward it; then record it with runpane inbox ack <item> --decision <D-n>. When runpane inbox lists the answer with undelivered, Pane can't type it there: pass it on with runpane panels submit, unless the reason is that its tab closed (no worker is left to tell). A tab can also receive closed: Q-n (by you) or correction: Q-n → <answer> (by you): that is the person's word. Anything the person types comes back as a reply instead: your call's result {reply, picks?, id, next}, or reply: Q-n → "<their words>" (picked: <picks>) (by you) in your tab (a relayed worker question's reply goes to the worker's tab, and to you when the worker can't read it). If it answers, run runpane notify --took <id|Q-n> answer and act on it. If it's a question, run runpane notify --took <id|Q-n> question at once, find out, then run runpane notify --update <id|Q-n> --asked "<their question>" --what-changed "<one line per change>" in the background with any changed --title, --context, --choices, --questions or --links (a reworded choice carries "was": "<old label>"); it saves a new version of the same approval and waits like a notify call, so never post a second approval for it. correction: Q-n → that was a question: "…" (by you) means update the approval; correction: Q-n → that was your answer: "…" (by you) means it is recorded as the answer: act on it. Stop the background call of an item that went stale. When you start, resume or wake, reconcile against the status files, your ledger and runpane inbox: relay or post what is missing, stop what is stale, and record each answer that has no decision. Approvals survive a restart, and an item whose asker is gone closes by itself. If notify returns unavailable because Approvals is off, ask in this tab as before.${record?.runtime === 'wsl' ? `\n${wslRouting(record)}` : ''}`; } diff --git a/main/src/services/skillCacheManager.test.ts b/main/src/services/skillCacheManager.test.ts index 1f62edea2..d20a93fd5 100644 --- a/main/src/services/skillCacheManager.test.ts +++ b/main/src/services/skillCacheManager.test.ts @@ -405,7 +405,7 @@ process.stdout.write(JSON.stringify(payload) + '\\n'); expect(variant).toContain('`reply: Q-n → "<their words>" (picked: <picks>) (by you)`'); expect(variant).toContain('`runpane notify --took <id|Q-n> question`'); expect(variant).toContain('never post a second approval for it'); - expect(variant).toContain('a title of about 80 characters'); + expect(variant).toContain('about 80 to 100 characters on one line'); expect(variant).toContain('`runpane inbox ack <item> --decision <D-n>`'); expect(variant).toContain('stop its background command (TaskStop in Claude Code)'); expect(variant).toContain('When you start, resume or wake, reconcile your open background calls'); diff --git a/main/src/services/skillCacheManager.ts b/main/src/services/skillCacheManager.ts index befbe978f..16123eef8 100644 --- a/main/src/services/skillCacheManager.ts +++ b/main/src/services/skillCacheManager.ts @@ -8,6 +8,7 @@ import type { AppConfig } from '../types/config'; import type { Project } from '../database/models'; import { syncPaneHomeSkill } from './paneHomeSkill'; import { keepSessionTidySection } from './sessionTidyRules'; +import { APPROVALS_RULES } from './approvalsRules'; // Every skill Pane installs for its agents ships with Pane. const PANE_CHAT_BUNDLE_ROOT = path.join(__dirname, 'paneChatBundle'); @@ -116,7 +117,9 @@ wrapper named by the runtime context or Pane checkout, after checking its version and doctor result and repeating the command check. If no verified wrapper is available, report one concise blocker and wait.`; -const APPROVALS_SECTION = `## Approvals: what waits on the person (while Approvals is on) +const APPROVALS_SECTION = `${APPROVALS_RULES} + +## Approvals: what waits on the person (while Approvals is on) You are in orchestrator mode: this Session's work moves through you. @@ -134,15 +137,9 @@ You are in orchestrator mode: this Session's work moves through you. - **At the end of a turn**, either have an approval waiting for the person in Approvals that moves the work on, or be done and say so. A turn that ends while your workers run, with your watch armed, is fine. -- **Ask through Approvals, always with context:** say in \`--context\` why it - needs them and what each option means, list the options with the one you - recommend first, and pass the related files, PRs and pages as \`--links\`. - Pane builds the approval's Summary from these fields, so: a title of about - 80 characters, context as why it matters, each choice with what it does - (\`detail\`) and one \`recommended\`, and links to every reference, other - approvals included (\`{"label":"Q-12","approval":"<id|Q-n>"}\`). Related - questions go in one options card with \`--questions - '[{"title":"<question>","choices":[...]},...]'\` instead of \`--choices\`. +- **Ask the way "Asking the person in Pane" above says.** As this + Session's orchestrator you post every approval card yourself, with + \`runpane notify\`, and relay a worker's question into its card. The rest of this section is how, while Approvals is on in Pane (its Settings switch, on by default). @@ -162,8 +159,7 @@ runpane notify --ui options --title "Q-7: <the question>" --context "<what it is \`\`\` The CLI call waits until the person answers. The MCP \`notify\` tool returns - \`waiting\` after a while instead, so don't use it for this, and don't block - your turn on \`AskUserQuestion\`. + \`waiting\` after a while instead, so don't use it for this. - A worker's blocked question already has a card: relay into it with \`runpane notify --relays <item> ...\` (the id from \`runpane inbox\`), which improves that card and rings it. diff --git a/packages/runpane-py/src/runpane/generated_contract.py b/packages/runpane-py/src/runpane/generated_contract.py index aec833ec9..0e8bcafbd 100644 --- a/packages/runpane-py/src/runpane/generated_contract.py +++ b/packages/runpane-py/src/runpane/generated_contract.py @@ -1,4 +1,4 @@ # Generated by scripts/generate-runpane-contract.js. Do not edit by hand. import json -RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \"^22.23.4 || ^24.22.1 || ^26.12.1\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false,\n \"watch\": {\n \"follow\": {\n \"summary\": \"What `runpane watch --follow` does with no other flags: quiet, batched updates and expiry after 3 hours with nothing happening. Individual duration flags override the defaults.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.ready\",\n \"agent.blocked\",\n \"agent.idle\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"pane.gone\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\"\n ],\n \"readySettle\": \"3m\",\n \"blockedSettle\": \"30s\",\n \"minInterval\": \"10m\",\n \"idleAfter\": \"10m\",\n \"idleBackoff\": true,\n \"expireAfter\": \"3h\",\n \"heartbeat\": \"off\",\n \"includeShells\": false,\n \"heldInputDetection\": true\n },\n \"untilEvent\": {\n \"summary\": \"What `runpane watch --until-event` does with no other flags: waits for the first event worth waking an agent for, prints it immediately without follow settling or batching, and exits 0.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.blocked\",\n \"panel.exited\",\n \"pane.gone\"\n ]\n }\n }\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\",\n \"cursor\",\n \"opencode\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n },\n \"cursor\": {\n \"title\": \"Cursor\",\n \"command\": \"cursor-agent --force --trust\",\n \"description\": \"Open a Cursor Agent terminal tab and allow the initial input to drive the agent.\"\n },\n \"opencode\": {\n \"title\": \"OpenCode\",\n \"command\": \"opencode --auto\",\n \"description\": \"Open an OpenCode terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"terminalKeys\": {\n \"enter\": \"\\r\",\n \"escape\": \"\\u001b\",\n \"tab\": \"\\t\",\n \"backspace\": \"\",\n \"space\": \" \",\n \"up\": \"\\u001b[A\",\n \"down\": \"\\u001b[B\",\n \"right\": \"\\u001b[C\",\n \"left\": \"\\u001b[D\",\n \"ctrl-c\": \"\\u0003\",\n \"ctrl-d\": \"\\u0004\"\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true,\n \"localControl\": false\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true,\n \"localControl\": false\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update both Pane and the invoked supported CLI through an independent helper.\",\n \"usage\": [\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\"\n ],\n \"target\": \"client\",\n \"localControl\": false\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version [--pane-dir <path>]\",\n \"runpane --version\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, runpane-on-PATH, and remote setup diagnostics.\",\n \"openWorld\": true,\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"runpane doctor --fix [--json] [--pane-dir <path>]\",\n \"runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"usage\": [\n \"runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command <command> [--json]\"\n ],\n \"toolsets\": [\n \"admin\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"usage\": [\n \"runpane mcp [--toolsets <name,...>] [--read-only]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path <path> [--name <name>] [--json] [--yes] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"usage\": [\n \"runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"usage\": [\n \"runpane repos update --repo <selector> [--name <name>] [--build-script <script>] [--run-script <script>] [--archive-script <script>] [--worktree-folder <path>] [--open-ide-command <command>] [--system-prompt <text>] [--env <json>] [--env-remove <name,...>] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos rename --repo <selector> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"usage\": [\n \"runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"usage\": [\n \"runpane secrets set --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"localControl\": true\n },\n {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"usage\": [\n \"runpane secrets delete --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"usage\": [\n \"runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... [--pane-dir <path>] -- <command>...\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"usage\": [\n \"runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"usage\": [\n \"runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace state\",\n \"summary\": \"Read one workspace snapshot of every Pane and CLI panel.\",\n \"usage\": [\n \"runpane workspace state [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceStateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"usage\": [\n \"runpane workspace list [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"usage\": [\n \"runpane workspace enable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"usage\": [\n \"runpane workspace disable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"usage\": [\n \"runpane workspace [<machine>] read <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"usage\": [\n \"runpane workspace [<machine>] write <path> [--content <text>] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"usage\": [\n \"runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"usage\": [\n \"runpane handoff --template\",\n \"runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace agent and Pane transitions using a daemon-held cursor.\",\n \"usage\": [\n \"runpane watch [--follow|--until-event] [--session <id|name>|--pane <id>] [--json] [overrides]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceWaitRequest\",\n \"workspaceWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"usage\": [\n \"runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --from-json <path|-> [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane without changing the worktree.\",\n \"usage\": [\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [--resume <id>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\",\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"usage\": [\n \"runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes pin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"usage\": [\n \"runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"usage\": [\n \"runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"usage\": [\n \"runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels) on explicit user request.\",\n \"usage\": [\n \"runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent or terminal tab inside an existing Pane, sharing its worktree. The default for review, QA, fixes and any other agent on work that already has a Pane.\",\n \"usage\": [\n \"runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"usage\": [\n \"runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"localControl\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"usage\": [\n \"runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--source user|agent] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place, resuming its agent's conversation.\",\n \"usage\": [\n \"runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"usage\": [\n \"runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"usage\": [\n \"runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"usage\": [\n \"runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane panels conversation --hook [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"usage\": [\n \"runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"sessions:get-git-status\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"usage\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stage-and-commit\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"usage\": [\n \"runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-push\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"usage\": [\n \"runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-pull\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"usage\": [\n \"runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:rebase-main-into-worktree\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"usage\": [\n \"runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:restore\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"usage\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:squash-and-rebase-to-main\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash-pop\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"usage\": [\n \"runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-soft-reset\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"usage\": [\n \"runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-fetch\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"usage\": [\n \"runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:run-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"usage\": [\n \"runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:stop-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"usage\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"folders:move-session\",\n \"args\": [\n \"--pane\",\n \"--folder\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"usage\": [\n \"runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"folders:get-by-project\",\n \"args\": [\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"usage\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"folders:create\",\n \"args\": [\n \"--name\",\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"usage\": [\n \"runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \"runpane links create --repo <repo-id> [--json]\",\n \"runpane links create --session <session-id> [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"links\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"usage\": [\n \"runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"runpane:links:open\",\n \"args\": [\n \"--url\"\n ]\n },\n \"toolsets\": [\n \"links\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"usage\": [\n \"runpane docs search --query <text> [--limit <count>] [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"usage\": [\n \"runpane docs read --doc <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"usage\": [\n \"runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"usage\": [\n \"runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"usage\": [\n \"runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"usage\": [\n \"runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"usage\": [\n \"runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \"runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"runpane notify --resume <id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"usage\": [\n \"runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"usage\": [\n \"runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"usage\": [\n \"runpane autopilot status [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"usage\": [\n \"runpane autopilot waiting [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"usage\": [\n \"runpane autopilot stack [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"usage\": [\n \"runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"usage\": [\n \"runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"usage\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"usage\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"usage\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"usage\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"usage\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"usage\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"usage\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"usage\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"usage\": [\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"usage\": [\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"usage\": [\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"usage\": [\n \"runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"usage\": [\n \"runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"usage\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"usage\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"usage\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"usage\": [\n \"runpane loops list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"usage\": [\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"usage\": [\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"usage\": [\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"usage\": [\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"localControl\": true\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository, created/renamed pane session, named lock, or loop.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name. For a loop, the worktree and branch name template; defaults to loop-<name>.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"description\": \"Exact new branch name for the created worktree, slashes included. Must not exist yet; defaults to the worktree name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"aliases\": [\n \"--base\"\n ],\n \"description\": \"Ref the created worktree branches from. --base is an alias.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"description\": \"Top-level sidebar folder for an adopted pane.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<agent-session-id>\",\n \"description\": \"Agent session id to persist and resume in an adopted pane.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"description\": \"http(s) or file URL for panels open; pane:// link for links open.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"description\": \"File inside the Pane worktree to open; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"aliases\": [\n \"--prompt-file\"\n ],\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--attach\",\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a structured JSON request payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create/adopt --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated. On panels conversation it is instead what started the conversation: startup, resume, clear, or compact.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"description\": \"Use a persisted daemon-held workspace cursor.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"description\": \"Read workspace events after this generation.\"\n },\n {\n \"name\": \"--from\",\n \"value\": \"<now|earliest>\",\n \"description\": \"Choose where a new named cursor starts.\"\n },\n {\n \"name\": \"--kinds\",\n \"value\": \"<kind,...>\",\n \"description\": \"Limit workspace events to comma-separated kinds.\"\n },\n {\n \"name\": \"--name-contains\",\n \"value\": \"<text>\",\n \"description\": \"Limit workspace events to Pane names containing text.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane from workspace watch results; repeatable.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"description\": \"Print a HEARTBEAT line this often under --follow (a duration; default off).\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"description\": \"First IDLE reminder after a READY panel stays idle (a duration; default 10m under --follow; 0 turns IDLE off).\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"description\": \"End a --follow watch after this long with nothing but IDLE, printing WATCH EXPIRED and exiting 0 (a duration; default 3h; 0 never expires).\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Quiet time before READY under --follow (a duration; default 3m).\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Time before BLOCKED under --follow (a duration; default 30s).\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"description\": \"Batch window for non-urgent lines under --follow (a duration; default 10m).\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read diagnostic report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"description\": \"Named orchestration Session id or exact name.\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"description\": \"How long a named lock is held before it expires: a number with ms, s, m, or h (such as 30m); a bare number is milliseconds. At least 1s, at most 24h.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"description\": \"lock acquire: how long to wait for a held lock to come free; 0 (the default) returns at once. On panels submit, panels create and panes create, --wait takes no value: block until that panel reports, misses its report, waits on a prompt, or exits, then print one JSON result.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"description\": \"With --wait: give up after this long (30m, 2h; 0 waits without limit) and exit 124.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"description\": \"What a named lock is for. Outside a Pane terminal it also names the owner, so pass the same note to release it.\"\n },\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"description\": \"Five-field cron schedule in this machine's local time zone, such as \\\"0 3 * * *\\\". Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"description\": \"Commit message for panes commit.\"\n },\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"description\": \"Search words for docs search.\"\n },\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"description\": \"Doc path for docs read.\"\n },\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"description\": \"MCP toolsets for runpane mcp.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"description\": \"Named keys for panels input, such as down,enter.\"\n },\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"description\": \"Report state for runpane report.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"description\": \"Pull request number for runpane report.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"description\": \"Head commit (7-40 hex characters) for runpane report.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"description\": \"Report summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read the report summary from a file or stdin.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"description\": \"What a blocked worker needs answered; required with --state blocked.\"\n },\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"description\": \"The conversation (agent session) id a panel's agent is on, for runpane panels conversation.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"description\": \"Working directory on the target machine for workspace exec.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"description\": \"Handoff note to check and send; - reads stdin.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"description\": \"Handoff destination machine; overrides the machine in the destination text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"description\": \"Model for the receiving agent; overrides the destination text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"description\": \"Reasoning effort for the receiving agent (minimal, low, medium, high, xhigh, max); overrides the destination text.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository. Defaults to worktrees/ in the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME|json>\",\n \"description\": \"secrets exec: inject secret NAME as VAR; repeatable. repos update: a JSON array of project variables to add or replace by key.\"\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"description\": \"Pane secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"description\": \"Secrets to inject, comma-separated. Defaults to every secret in the project.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"description\": \"secrets request: why you need the secret, shown on the card. autopilot close: why it closes, recorded on the item.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"description\": \"notify: what to show: pick one of choices, approve or deny, type an answer, or an update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"description\": \"notify: background shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options: 1 to 9 choices as a JSON array of {label, detail?, recommended?}; with --update a reworded choice adds \\\"was\\\", its old label.\"\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: a preformatted block, such as the command or SQL to approve.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: label of the disclosure that reveals --detail, such as Show the SQL; up to 40 characters.\"\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui ask: suggested answers as a JSON array of strings.\"\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui info: one button as a JSON object {label, url}.\"\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"description\": \"notify: up to 6 related files, pages or approvals as a JSON array of {label, path}, {label, url} or {label, approval}; any ui.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"description\": \"Text that workspace write writes instead of stdin.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"description\": \"Start an Agent Farm profile (such as greenfield/implementer:opus) in a chat tab.\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"description\": \"A launch argument for --agent-farm, shown to the agent in its LAUNCH CONTEXT; repeatable.\"\n },\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"description\": \"autopilot stack push only: the waiting item (an id from `runpane autopilot waiting`) to put on the stack.\"\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"description\": \"notify only: relay into this waiting item (an id from `runpane inbox`): the same card gains the choices, context and links, and the call waits on it.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"description\": \"notify only, for Autopilot's own items: the Session the item is about.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options, instead of --choices: 2 to 4 questions as a JSON array of {title, choices}, each with 1 to 9 choices and one pick.\"\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: say how you took the person's reply to this approval; answer or question follows the id.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"description\": \"notify --took: the word after the id, as a flag (the MCP tool's tookAs).\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"description\": \"notify --took <id> question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: save a new version of this approval after the person's question, and wait on it.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: what changed, one line per change.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"description\": \"inbox ack only: record this D-n on each named item.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"description\": \"autopilot answer only: answer with this choice's label.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"description\": \"autopilot answer only: why, recorded on the item: a D-n, a document or the person's words.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--split\",\n \"description\": \"Open the tab in split view beside the primary tab group (default for panels open).\"\n },\n {\n \"name\": \"--tab\",\n \"description\": \"Open the tab in the current tab group instead of split view. On autopilot stack push it takes a value instead: --tab <path-or-url> adds a tab (a file, or an http(s) URL); repeatable.\"\n },\n {\n \"name\": \"--pinned\",\n \"description\": \"Request pinning at creation; Session association still clears the first pin. Outside a Session, creation pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"description\": \"Create the pane unpinned instead of the default pinned (the UI's favorite/pin star).\"\n },\n {\n \"name\": \"--no-associate\",\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session.\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn, or a tab holds unsent input; for panels close, close the tab even if an agent opened it, its agent is busy, or it holds unsent input (a stuck tab); for lock release, release another owner's lock.\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"description\": \"Also check and remove an adopted Pane's worktree when archiving it. The local branch is kept.\"\n },\n {\n \"name\": \"--merged\",\n \"description\": \"With panes archive --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work. Other Panes are skipped with a reason (pr-open, pr-not-merged, pr-status-unknown, or a local safety code).\"\n },\n {\n \"name\": \"--closed\",\n \"description\": \"With panes archive --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n {\n \"name\": \"--launch\",\n \"description\": \"Run an adopted pane's agent resume command immediately instead of staging it.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--follow\",\n \"description\": \"Continue waiting for workspace events until interrupted.\"\n },\n {\n \"name\": \"--until-event\",\n \"description\": \"Watch only: wait across daemon calls for the first matching event, print it, and exit 0. --timeout-ms becomes the total deadline (exit 124). For harnesses that wake an agent when a process exits.\"\n },\n {\n \"name\": \"--ack-now\",\n \"description\": \"Advance a named cursor immediately for at-most-once delivery.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"description\": \"Include up to 120 characters of unsubmitted composer text in ready events.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"description\": \"Resume every stopped agent panel in every non-archived Pane.\"\n },\n {\n \"name\": \"--include-shells\",\n \"description\": \"Include ordinary shell panels in follow mode.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"description\": \"Disable redacted held-input detection under --follow (STUCK lines, and heldInputPresent in JSON).\"\n },\n {\n \"name\": \"--self-test\",\n \"description\": \"Probe the current daemon watch path without advancing a named cursor.\"\n },\n {\n \"name\": \"--report\",\n \"description\": \"Prepare a redacted doctor report; external filing still requires --yes.\"\n },\n {\n \"name\": \"--fix\",\n \"description\": \"Update each npm-installed runpane older than the Pane-managed CLI to its version. Never removes a file.\"\n },\n {\n \"name\": \"--read-only\",\n \"description\": \"Serve only read-only MCP tools.\"\n },\n {\n \"name\": \"--template\",\n \"description\": \"Print the handoff note template, with this checkout's (or Session's) origin and git state filled in.\"\n },\n {\n \"name\": \"--push\",\n \"description\": \"Before a handoff, commit uncommitted work as WIP and push the branch (never forces).\"\n },\n {\n \"name\": \"--note-only\",\n \"description\": \"Hand off to a fresh agent with the note, instead of carrying and resuming your own session.\"\n },\n {\n \"name\": \"--interrupt\",\n \"description\": \"Stop the current agent turn, wait for an empty idle composer, then submit and verify the replacement.\"\n },\n {\n \"name\": \"--expect-report\",\n \"description\": \"Pane expects a `runpane report` and emits agent.report.missing if the agent goes ready or exits without one. The default for work an agent sends; use it to ask for one on work marked --source user.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"description\": \"Send work without waiting for a `runpane report` (fire-and-forget input); no agent.report.missing follows.\"\n },\n {\n \"name\": \"--all\",\n \"description\": \"inbox ack only: clear every item of the --session.\"\n },\n {\n \"name\": \"--paused\",\n \"description\": \"loops create only: create the loop paused.\"\n },\n {\n \"name\": \"--chat\",\n \"description\": \"Open the agent as a chat tab (Claude or Codex over ACP, no terminal) instead of a terminal tab.\"\n },\n {\n \"name\": \"--next\",\n \"description\": \"autopilot stack push only: put the entry right after the one on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--approve\",\n \"description\": \"autopilot answer only: approve the item.\"\n },\n {\n \"name\": \"--deny\",\n \"description\": \"autopilot answer only: deny the item.\"\n },\n {\n \"name\": \"--hook\",\n \"description\": \"runpane panels conversation: read a Claude Code SessionStart hook's JSON from stdin and report it for PANE_PANEL_ID. Prints nothing and always exits 0.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane sessions list [--json] [--pane-dir <path>]\",\n \" runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions pause|resume --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \" runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane loops list [--json]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read|write|exec|<command>\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \" --report Prepare a redacted, inspectable report\",\n \" --body-file <path|-> Read report evidence from a file or stdin\",\n \" --title <text> Report title\",\n \" --yes Confirm creating one greenfield-inc/Pane issue\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Remote daemon data directory (default: ~/.pane_remote)\",\n \" --pane-path <path> Existing Pane executable used to perform repair\",\n \" --yes Skip the restart confirmation\",\n \" --json Print one machine-readable repair result\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Repository display name\",\n \" --build-script <script> Build (setup) script; runs in each new worktree\",\n \" --run-script <script> Run script; runs on the Play button\",\n \" --archive-script <script> Archive script; runs before a worktree is removed\",\n \" --worktree-folder <path> Folder for new worktrees, absolute or relative to the repository\",\n \" --open-ide-command <command> Command the Open in IDE button runs\",\n \" --system-prompt <text> Project system prompt\",\n \" --env <json> Project variables to add or replace, a JSON array: [{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://...\\\"}, {\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]\",\n \" --env-remove <name,...> Project variables to remove\",\n \" --clear <setting,...> Clear settings, e.g. run-script,archive-script; cleared scripts fall back to the repo's config file\",\n \" --from-json <path|-> Settings object: {\\\"runScript\\\": \\\"pnpm dev\\\", \\\"archiveScript\\\": null, \\\"envVars\\\": {\\\"set\\\": [...], \\\"remove\\\": [\\\"OLD\\\"]}, ...}; flags override it\",\n \" --dry-run Show the result without saving\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Limit results to one saved repository\",\n \" --pane <pane-id> Return one Pane, including an idle Pane with zero usage\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read <path>\",\n \" runpane workspace <machine> write <path> (content from --content or stdin)\",\n \" runpane workspace <machine> exec -- <command>\",\n \" runpane workspace <machine> <runpane command> (any command that talks to Pane, e.g. sessions list --json)\",\n \" runpane workspace enable|disable\",\n \"\",\n \"<machine> is a Tailscale name, unique prefix, or IP. Your own machines are reachable, and a teammate's machine when its owner set Who can connect to Everyone on tailnet.\",\n \"Without <machine>, read, write, and exec route a path that cannot exist here (C:\\\\... on a Mac) to the one joined machine it fits.\",\n \"Commands that only run here (doctor, agent-context, mcp, docs) run there with: runpane workspace <machine> exec -- 'runpane doctor'\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes, a busy agent, or unsent input\",\n \" --remove-worktree Also check and remove an adopted Pane's worktree (the branch is kept)\",\n \" --session <id|name> Archive the Panes associated with this Session (requires --merged)\",\n \" --merged Only archive Panes that are clean and pushed, or merged via a PR\",\n \" --closed With --session --merged, also archive Panes whose PR closed unmerged\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Refresh and print archive safety evidence without archiving\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to pin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without pinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to unpin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without unpinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to rename\",\n \" --name <new-name> New non-empty Pane/session name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without renaming the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to focus\",\n \" --panel <panel-id> Optional panel/tab id inside the Pane to select\",\n \" --source <user|agent> Mutation source; does not change focus behavior\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Accepted and ignored; agent-context never contacts Pane\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \"\",\n \"Runs the Pane MCP server over stdio. Each runpane command with a JSON result becomes a tool that returns the same JSON as `runpane <command> --json`. Commands that need --yes take `yes: true` instead.\",\n \"Pane registers this server with Claude Code, Codex, and Cursor automatically (Settings > AI & Agents). To register it by hand:\",\n \" claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \" codex mcp add pane -- npx --yes runpane@latest mcp\",\n \"\",\n \"The MCP server ships in the npm package and inside the Pane app. The Python package does not include it.\",\n \"\",\n \"Options:\",\n \" --toolsets <name,...> core (default), all, read, repos, panes, panels, agents, sessions, workspace, git, docs, links, admin\",\n \" --read-only Serve only read-only tools\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tab to close\",\n \" --dry-run Report whether the tab would close, and why not, without closing it\",\n \" --force Close it even if an agent didn't open it, its agent is busy, or it holds unsent input\",\n \" --source <user|agent> Mark mutation source\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"\",\n \"Options:\",\n \" --query <text> Words to search for.\",\n \" --limit <count> Maximum results; defaults to 5.\",\n \" --json Print machine-readable output.\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"Read one Pane doc, help topic, or skill in full.\",\n \"\",\n \"Options:\",\n \" --doc <path> Path from docs search, such as docs/PANE_MCP.md.\",\n \" --json Print machine-readable output.\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --name <name> Name for the new Pane.\",\n \" --agent <codex|claude|cursor> Built-in agent to launch.\",\n \" --tool-command <command> Custom terminal command instead of a built-in agent.\",\n \" --prompt <task> The task to send once the agent is ready.\",\n \" --base-branch <branch> Branch to start the worktree from.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --limit <count> Screen lines to return; defaults to 40.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --text <message> The message to submit.\",\n \" --as-file-pointer Write the message to a prompt file and send `Read and follow <path>`.\",\n \" --no-expect-report Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" runpane loops list [--json] [--pane-dir <path>]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form; ~ is that machine's home.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form.\",\n \" --content <text> Text that replaces the file. Without it, stdin is written.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" --cwd <path> Working directory on that machine, in any path form; defaults to its home.\",\n \" --timeout-ms <milliseconds> Stop the command after this long (default 120000, at most 540000).\",\n \" --json Print machine-readable output.\",\n \" -- <command> The command line, run by that machine's shell.\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"Hand your task to another agent. You write the note (runpane handoff --template); the CLI checks it,\",\n \"makes sure your branch is pushed, sends the note to the destination, and starts the agent there.\",\n \"From a Pane tab, a handoff to the same agent (Claude, Codex, Cursor or OpenCode) carries your own session:\",\n \"another machine resumes a copy of it, this machine resumes a fork. The CLI then checks the receiver\",\n \"continued it (continued, inconclusive, or not-continued, which exits 1 and keeps the receiver).\",\n \"--note-only, another agent, or no session to carry starts a fresh agent with the note instead.\",\n \"From a Session's terminal it skips git and creates the Session on the other machine; a same-agent\",\n \"Session resumes your conversation when first opened, otherwise it reads the note. The sender Session stays.\",\n \"\",\n \"Options:\",\n \" <destination> Freeform: agent, model, effort, and machine, such as \\\"codex gpt-5 high on parsas-macbook-pro\\\".\",\n \" --template Print the note template, with this checkout's (or Session's) origin and git state filled in.\",\n \" --note-file <path|-> The note to check and send; every template section must be filled in.\",\n \" --machine <name> Destination machine (Tailscale name or unique prefix); overrides the text.\",\n \" --agent <codex|claude|cursor> Receiving agent; overrides the text.\",\n \" --model <model> Model for the receiving agent; overrides the text.\",\n \" --effort <level> minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\",\n \" --repo <selector> Saved repository on the destination; defaults to the one with the same remote.\",\n \" --push Commit uncommitted work (not the note) as WIP and push the branch first (never forces).\",\n \" --note-only Start a fresh agent with the note instead of carrying your own session.\",\n \" --dry-run Check destination reachability, saved repo and OS; print note and git state; send nothing. From a Session: the name it would get.\",\n \" --json Print machine-readable output.\",\n \"\",\n \"Examples:\",\n \" runpane handoff --template > ~/handoff.md\",\n \" runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \" runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" python -m runpane loops list [--json]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" python -m runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \" --report\",\n \" --body-file <path|->\",\n \" --title <text>\",\n \" --yes\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --yes\",\n \" --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --name <name>\",\n \" --build-script <script>\",\n \" --run-script <script>\",\n \" --archive-script <script>\",\n \" --worktree-folder <path>\",\n \" --open-ide-command <command>\",\n \" --system-prompt <text>\",\n \" --env <json>\",\n \" --env-remove <name,...>\",\n \" --clear <setting,...>\",\n \" --from-json <path|->\",\n \" --dry-run\",\n \" --yes\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" python -m runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" python -m runpane secrets list [--project <project>] [--json]\",\n \" python -m runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" python -m runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | python -m runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" python -m runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \"\",\n \"runpane workspace list|read|write|exec|enable|disable ship in the npm package: npx --yes runpane@latest workspace list\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" python -m runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --remove-worktree\",\n \" --session <id|name>\",\n \" --merged\",\n \" --closed\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --name <new-name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --panel <panel-id>\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \" --pane-dir <path> Accepted and ignored\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp\",\n \"\",\n \"The Pane MCP server ships in the npm package and inside the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest mcp\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --dry-run\",\n \" --force\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"python -m runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"python -m runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"python -m runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"python -m runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"python -m runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"python -m runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"python -m runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"python -m runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"`runpane docs search` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs search\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"`runpane docs read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs read\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents start` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents start\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents status` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents status\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents send` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents send\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" python -m runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"python -m runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"python -m runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"python -m runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" python -m runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" python -m runpane loops list [--json] [--pane-dir <path>]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"python -m runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace list` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace list\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace enable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace enable\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace disable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace disable\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"`runpane workspace read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace read\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"`runpane workspace write` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace write\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"`runpane workspace exec` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace exec\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"`runpane handoff` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest handoff\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"python -m runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"python -m runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane mcp\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane repos get --repo Pane --json\",\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --worktree-folder ../pane-worktrees --yes --json\",\n \"runpane repos update --repo Pane --clear run-script --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://api.test\\\"},{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\",\n \"runpane repos rename --repo 3 --name Lev --yes --json\",\n \"runpane secrets list --json\",\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes\",\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\",\n \"runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\",\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook tests against Stripe test mode.\\\" --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes cost --pane <pane-id> --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock release --name testing-account --json\",\n \"runpane lock list --json\",\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops list --json\",\n \"runpane loops run --loop <loop-id> --yes --json\",\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\",\n \"runpane agents status --pane <pane-id> --json\",\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane panels last-message --panel <panel-id> --json\",\n \"runpane panels conversation --conversation <id> --source resume --json\",\n \"runpane panels close --panel <panel-id> --yes --json\",\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\",\n \"runpane panes pause --pane <pane-id> --yes --json\",\n \"runpane panes resume --pane <pane-id> --yes --json\",\n \"runpane docs search --query \\\"archive a pane\\\" --json\",\n \"runpane links create --pane <pane-id> --json\",\n \"runpane panes git-status --pane <pane-id> --json\",\n \"runpane help\",\n \"runpane <command> --help\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. The remote-host wizard asks only for a name, then runs interactive Tailscale setup with automatic port selection; explicit install daemon flags remain available for SSH and manual URLs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` defaults to both app and active supported CLI, with independent supervision, staging, durable status, and explicit restoration outcomes. Prerequisites, status fields/exit codes, draft handling and manual recovery are documented in [Update recovery](UPDATE_RECOVERY.md).\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, every `runpane` on `PATH` against the Pane-managed CLI, and remote-daemon hints. `--fix` updates npm installs of `runpane` older than the Pane-managed CLI to its version and never removes a file. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane daemon repair` rewrites and restarts only the managed remote-daemon launcher/service. It never creates pairing credentials, changes tunnels, or downloads Pane; doctor only recommends it and never runs it automatically.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output. An unknown command exits 2 and lists the closest command names; with `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout.\",\n \"`runpane mcp` runs a stdio MCP server whose tools are generated from this contract: every command with result `jsonSchemas` becomes a tool that runs `runpane <command> --json` and returns its output. Only the npm package and the Pane app include it; the Python wrapper prints how to run it with Node and exits non-zero.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane repos get` prints a saved repository's Project Settings. Each script shows its saved value, the fallback from the repository's config file, and the one Pane will run.\",\n \"`runpane repos update` changes a saved repository's Project Settings through the same save as the Project Settings dialog. It requires `--yes`, supports `--dry-run`, clears settings with `--clear`, and adds, replaces or removes project environment variables with `--env` and `--env-remove`.\",\n \"`runpane repos rename` trims and sets a saved repository's display name, the same rename as the repository settings in the app, and returns the updated repository record.\",\n \"`runpane secrets` manages Pane secrets: named values, addressed as `<project>/<NAME>`, that every machine saved in Pane keeps a copy of. `list` shows names, times and machines only. `set` reads the value from stdin. `delete` removes one everywhere. `exec` runs a command with secrets in its environment and returns its exit code. `request` asks the person on a masked Notify card and returns only `saved` or `declined`. No command prints a value, and the MCP server has no tool that sets or reads one. These commands run on the machine itself, so `runpane workspace <machine> secrets ...` is refused; use `runpane workspace <machine> exec -- runpane secrets ...`.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes cost` reports estimated token costs per Pane for the last 30 days, including per-model breakdowns and cache efficiency; unscoped output includes an Unattributed bucket that reconciles against workspace totals.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed. New Panes are pinned into the UI's favorite/pin set by default, except when the CLI runs inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID`), where child worktrees default to unpinned. Explicit `--pinned` / `--no-pinned` override creation defaults. First Session association clears an existing pin, including an explicit creation pin; use `panes pin` after association to pin a child. This avoids duplicating Session children in the global pinned list. Manual pins applied afterward are preserved. Panes created interactively in the Pane UI are unaffected. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"For `panes create --wait-ready` (and `panels create`, `panes adopt --launch`), ready means an identified agent at its own input box: declared with `--agent`, or detected from the command, its foreground process or its screen. A `--tool-command` that never shows a supported agent is not ready: `readiness.ok` is false with `readiness.reason.code` `no-agent-detected` after the timeout, or `command-exited` as soon as the command exits back to the shell, and nothing is typed into it. `agent-not-ready` means the agent was detected but its input box was not ready in time. CLI readiness is confirmed on consecutive polls. Codex also requires a recognized configured model or context footer below its live composer; its provisional startup composer is not ready. Hidden or unrecognized Codex status lines conservatively time out rather than prove readiness. Startup prompts return blocked with the prompt's own text in blocked.message and a suggestedCommand, and Pane never answers them, apart from Claude's folder trust for a Session's own folder: first-run trust and daemon dialogs and Agent Farm's `Trust this repository workspace? [y/N]` as blocked.kind=first-run-dialog; Codex's security setup banner and any other prompt, including a wrapper's or shell's `[y/N]` before the agent appears, as agent-prompt. A starting message blocked this way is held and sent once the prompt is answered and the agent's input box is ready. Every create with a starting message returns `initialInput`. Pane stages the text in the composer, presses the agent's submit key separately (Ctrl+Enter for Codex, declared or detected), and checks where it went: `initialInput.delivery` is `taken` or `queued` (from the agent's transcript or its screen), `in-composer`, or `unknown` (`argv` means a prompt was routed at launch; Pane gives the transcript up to 10 seconds before settling on unknown). `initialInput.verifiedSubmitted` is true exactly when it is `taken` or `queued`, and the create's `ok` is false unless a Claude or Codex starting message is verified. Cursor and OpenCode have no acceptance probe: their delivery stays `unknown` with a delivery.message saying so, `verification: unverifiable`, and `ok` follows readiness. Without `--wait-ready` the create returns at once with `initialInput.verifiedSubmitted: false`, a delivery.message saying the call did not wait, and a `panels wait` nextCommand; Pane still sends the message once the agent is identified and at its input box, and gives up without typing it if no supported agent appears within 30 seconds.\",\n \"`runpane panes archive` refreshes the configured upstream, reports exact unpushed commit evidence, and refuses unsafe archive operations unless `--force` is used. A branch whose upstream is gone counts as pushed when a merged GitHub pull request has HEAD as its head (`safetyCheck.mergedViaPr`). Add `--dry-run` to inspect the same evidence without archiving. `--remove-worktree` applies the same check and removal to an adopted worktree; local branches are always kept. Successful archives report `worktreeCleanup: completed` once the worktree is gone from its path and from git; `trashDeletion: pending` means its files are still being deleted in the background. `runpane panes archive --session <id|name> --merged` archives every Session Pane that is clean and pushed or merged, and reports a reason for each skipped Pane. Archive refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless `--force` is used, and the Session sweep skips it. Add `--closed` to the sweep to also archive Panes whose PR closed without merging.\",\n \"`runpane panes rename` trims and updates a Pane's display name without changing its worktree, branch, panels, or focus, and returns the updated pane summary.\",\n \"`runpane panes focus` raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Because it steals the user's window focus, run it only on an explicit user request to open, focus, show, or switch to a Pane; never focus a Pane proactively, the same doctrine that keeps `panes create` background/no-focus for `--source agent`.\",\n \"`runpane panels open` opens a URL or a file from the Pane worktree as a tab in an existing Pane (default: the calling panel's Pane from `PANE_SESSION_ID`), in split view beside the agent unless `--tab` is passed. Files open in an editor tab with read-only previews for supported formats, and HTML opens as editable source; an existing tab showing the same target is reused. It brings the tab forward for clients already showing that Pane, and never raises or focuses the Pane window or switches anyone's Pane.\",\n \"`runpane panels close --panel <panel-id>` closes a tab exactly like closing it in Pane (a terminal tab's process stops) and never archives the Pane or touches its worktree. Only a tab an agent opened with `--source agent` can close; Pane records that opener, and `panels list` shows it as `openedBy`. It refuses, without closing, the person's tabs and every tab from before Pane recorded openers (`opened-by-user`), permanent tabs (`permanent`), an agent mid-turn or waiting for an answer (`agent-busy`), unsent composer text or unsaved editor edits (`unsent-input`), and a live tab whose input Pane cannot read, such as agents other than Claude and Codex or chat tabs (`input-unknown`). A refusal prints `{ ok: false, closed: false, refused: { code, message, next } }` and exits 1. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports `wouldClose` and any refusal without closing. `--force` closes the tab anyway, as its close button does, for a stuck tab or one the person asked to close (`forced: true`); only a permanent tab still stays.\",\n \"`runpane panels list` lists tool panels inside one Pane session. Each panel with a saved agent session ID reports it as `resumeId`, with `resumeSource` (`agent-farm`, `configured`, or `agent`) naming the rule that reopens the conversation.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"`runpane panes create --branch <name>` creates the Pane's worktree on exactly that new branch, slashes included (for example `agents/w5a`). The name is checked with `git check-ref-format --branch`, creation fails if the branch already exists, and Pane never renames it to make it unique. `--worktree-name` still names the directory and defaults to `--name`. `--base` is an alias for `--base-branch`, and `--prompt-file` for `--initial-input-file`. `panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`. When the base is a remote-tracking branch (the default `origin/main`, or one named with `--base`), Pane fetches it first, waiting at most about 10 seconds, so the Pane starts at the remote's current tip. Each created item reports `base` (`ref`, `commit`, `refresh`); if the fetch fails the Pane still starts, from the local ref, and `base.notice` says so and how to catch up.\",\n \"`runpane panes adopt --launch` accepts `--prompt`, `--prompt-file`, `--wait-ready`, and `--ready-timeout-ms`, and reports `readiness`, `initialInput`, and `nextCommand` like `panes create`. A prompt without `--launch` is an error rather than being dropped.\",\n \"If composer submission cannot be verified without risking a duplicate, the create item is unsuccessful with `initialInput.staged`, `initialInput.attempts`, `initialInput.blocked.kind: submission_unverified`, and an actionable `nextCommand`. The CLI-facing `--prompt` alias maps to this canonical `initialInput` result.\",\n \"When running from WSL while Pane is installed on Windows, a Linux-installed runpane cannot reach Pane: it looks for a missing `/tmp/pane-daemon.../daemon.sock`. Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane` from any WSL shell) runs the Windows CLI through WSL interop and converts path arguments. With a Pane build older than that shim, invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"`runpane watch` waits for agent, Pane, panel, and PR events from the daemon journal without polling. `--follow` keeps watching with defaults tuned for an agent watching unattended (quiet, batched, and it ends itself after 3 hours with nothing happening); individual duration flags override these defaults. Cadence requires a named follow consumer; `--follow --since <generation>` has no consumer name and bypasses settling and batching. `--until-event` waits for one event worth waking for, prints it immediately without follow settling or batching, and exits 0. The defaults are listed in the contract under defaults.watch.\",\n \"A watch releases its pending slot when its client socket or named pipe disconnects. Re-arming the same named cursor takes over the previous request; the superseded watcher exits with a WATCH ERROR instead of competing for the cursor. Anonymous concurrent watchers retain the eight-wait limit. Each request also has a 125-second lease (slightly longer than the maximum 120-second long poll), so an abandoned request is reclaimed even if no disconnect arrives.\",\n \"The desktop app treats one consumer name specially: while a `runpane watch --as dictate` request is in flight, and for 30 seconds after the last one ends, Pane skips its own \\\"needs your input\\\" and \\\"finished\\\" desktop notifications, because [Dictate](https://github.com/parsakhaz/open-dictate) shows those events on its pill. When that watcher stops, Pane's notifications come back. The CLI's flags and output are unchanged.\",\n \"`sessions list` list durable named orchestration Sessions.\",\n \"`sessions create` create a durable named orchestration Session and its hidden terminal owner.\",\n \"`sessions get` read one durable named orchestration Session.\",\n \"`sessions update` update a Session overview from structured JSON.\",\n \"`sessions pin` and `sessions unpin` set the Session pin star through the existing UI update path. Create JSON accepts `isPinned` (default false); update JSON preserves the pin when the field is omitted.\",\n \"`sessions set-agent` switch the durable terminal agent for a named Session.\",\n \"`sessions associate` associate a user-visible Pane with a named Session.\",\n \"`sessions detach` detach a Pane from a named Session.\",\n \"`sessions overview` read a live status, activity, git, and pull request overview for a named Session. Each panel in `panes[].panels[]` carries its own latest `runpane report` as `report`, so one call shows what every worker last reported and when (`reportedAt`, next to the panel's `state` and `lastActivityAt`). When a report's summary is a complete JSON object, such as a status file sent with `--summary-file`, `report.data` holds it exactly as the worker gave it; Pane never reads, renames or validates its fields. A summary that is not a JSON object (plain text, an array, a scalar or invalid JSON), is nested more than 64 levels deep, or was truncated (`summaryTruncated`), has no `data`. A panel that never reported has no `report`. `panes[].report` is still the newest report among the Pane's panels. Each panel also has `type` (`terminal`, `browser`, `editor`, `explorer`, `diff`, ...): a Pane linked whole lists every tab, with `explorer` and `diff` last, and a link that names tabs lists exactly those.\",\n \"`runpane lock acquire|release|list` coordinate a resource shared between agents, such as one test account. The caller's Pane and panel own the lock; it is scoped to the owner's Session (or global outside one), renews for the same owner, and is released on TTL expiry, owner panel exit, or owner Pane archive. `--wait` blocks in the daemon until the lock comes free.\",\n \"`runpane loops list|create|update|run|pause|resume|delete` manage loops (scheduled tasks): a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, agents use their harness's own sleep and wake. See [LOOPS.md](LOOPS.md).\",\n \"`runpane agents start|status|send` finish the three common agent jobs in one call each: start an agent on a task (a new tab in the work's Pane with `--pane <id>`, or a new Pane for new work), check on it, and send it a follow-up.\",\n \"`runpane report --state ready|blocked|failed|done` is how a worker hands back its result. It stores the latest report on the worker's panel (state, `--pr`, `--head`, up to 16,000 characters of `--summary` or `--summary-file`, and the `--question` a blocked worker needs answered), journals an opt-in `agent.report` watch event (`REPORT <pane-name> pane <pane-id> panel <panel-id> ready pr#747 fc5dce9`; it skips the `--min-interval` batch), records it as Session activity, and shows it in `agents status`, `panels list`, and `sessions overview` (each panel's own report as `panes[].panels[].report`, with a JSON object summary as `data`, and the Pane's newest as `panes[].report`). Inside a Pane terminal the panel comes from `PANE_SESSION_ID` and `PANE_PANEL_ID`; elsewhere pass `--pane` and `--panel`. It prints how many watches were waiting for the report and received it (`Delivered to 1 waiting watcher.`, `liveWatchers` in JSON). Zero means nobody was waiting right now, not that the report is lost: it stays on the panel, and a named-cursor watch receives it when it next waits.\",\n \"`runpane notify` asks the person a question, or tells them something, on screen wherever they are, and prints their answer: `--ui options` with 1 to 9 `--choices` returns `{answer, note?}` (the chosen label), or with 2 to 4 `--questions` (`[{title, choices}]`) one pick each, as `{answer, answers}`, `--ui approve` returns `{approved, note?}`, `--ui ask` returns `{answer}`, and `--ui info` returns `{delivered: \\\"screen\\\"|\\\"inbox\\\"}` at once. Any ui takes up to 6 `--links` (`{label, path}`, `{label, url}` or `{label, approval}` for another approval), shown as chips on the card. When the person types a reply it returns `{reply, picks?, id, next}` (or, with no open call, types a `reply:` line into the asking tab): the reply may answer the question or ask one. `runpane notify --took <id|Q-n> answer` records it as the answer; `--took <id|Q-n> question [--status <text>]` shows that the agent is getting more context, and `runpane notify --update <id|Q-n> --asked <text> --what-changed <text>` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` saves a new version and waits on it. `--took` prints `{took, id, next?}`. Only the reply's reader (the asker, the orchestrator that relayed it, or Autopilot) may run them. The call waits with no time limit. A `{waiting: true, id, next}` result means the wait should continue with `runpane notify --resume <id>`; `runpane mcp` asks for it only for clients whose tool calls time out. When Pane is not running or Notify is off it prints `{unavailable, next}` and exits 0, so the agent asks in its terminal. Invalid input fails before anything is shown, with a message naming the field. A Session orchestrator adds `--relays <item>` (an id from `runpane inbox`) to relay a worker's question: the same card gains the choices, context and links, the call waits on it as usual, and the answer also goes straight to the worker's tab. Autopilot's own item takes `--about-session <id|name>`, the Session it is about. It needs no `--yes`: asking the person is the point.\",\n \"Work sent to an agent through `runpane panels create`, `panes create` (with a prompt), or `panels submit` records that the sender waits for a `runpane report`, unless the request says `--source user` (Pane's own UI does) or `--no-expect-report`; `--expect-report` asks for one anyway. Typing into a tab never does. If the agent then goes ready or its panel exits without a report, Pane journals one opt-in `agent.report.missing` entry (`MISSING REPORT <pane-name> pane <pane-id> panel <panel-id> ready|exited`) whose `lastOutput` is the agent's last transcript reply or last screen lines; it skips the `--min-interval` batch and reaches only watchers that list it in `--kinds`. While the agent's screen shows background work (Claude's `· 1 shell` footer or a running background subagent row, Codex's `background terminal running` line), Pane waits and looks again every 10 seconds instead. `agent.blocked` entries carry `screenLines`, the prompt the agent waits on, and are journalled once a panel has stayed blocked for 2 seconds.\",\n \"The orchestrator guide prescribes submit without `--wait`, followed by its named watch; use one completion consumer. As an optional CLI route, `--wait` on `runpane panels submit`, `panels create`, and `panes create` turns the expected report on and blocks on that panel until the first `agent.report`, `agent.report.missing`, `agent.blocked`, `panel.exited`, or `pane.gone`, then prints one line of JSON (`dispatchWaitResult`): the outcome, ids, the report's state, summary, `summaryPath`, PR and head, `screenLines` for a prompt, or `lastOutput` for a missing report, never the transcript. It exits 0 for a report, 3 missing, 4 blocked, 5 exited or gone, and 124 when `--timeout <duration>` passes. It follows the journal from the position the daemon took before delivering the work, so nothing that happens right after delivery is missed. It reuses watch --until-event and returns indeterminate (exit 6) if the journal epoch changes or retained history is lost; inspect the panel and inbox before retrying. Report-expected dispatches are serialized per panel and refused before writing while a result or missing-output collection is outstanding. A send with --no-expect-report is a reply (an answer, or an addition to the current work): it is delivered while a report is outstanding and leaves that expectation in place. Then watch for the original result.\",\n \"`runpane inbox [--session <id|name>] [--json]` lists what a Session's orchestrator has not acknowledged: reports, missing reports, and on-screen questions (a question clears itself once its panel moves on), plus `answers`, what its workers were told, kept 7 days; an answer with no `decision` still needs its D-n. `runpane inbox ack <item-id>...` or `--all --session <id|name>` clears items, and `runpane inbox ack <item-id>... --decision <D-n>` records the D-n, even on an item already cleared. An orchestrator runs it when it starts or resumes, to catch anything that finished while it was down; Pane never injects the inbox into its turns.\",\n \"`runpane panels resume --panel <panel-id>` restarts a stopped terminal panel in place, keeping its panel id: an agent panel relaunches with its resume command (Claude `--resume <session>`, `codex resume <session>`, Agent Farm `agent-farm run <profile> --resume <id>` with the ID Agent Farm printed; the result's `agentSessionId` names it), the same restart the app runs when it shows the panel, without taking focus. A running panel is left alone (`action: \\\"already-running\\\"`); one whose agent exited back to its shell is stopped, and resume runs its saved command again in that shell (`action: \\\"resumed\\\"`). If that command exits at once, `--wait-ready` returns `ok: false` with `reason.code: \\\"command-exited\\\"` and the error line it printed. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope and skips paused ones, `--concurrency` at a time (default 3), then, with `--wait-ready`, waits on the resumed panels together, and returns `{ resumed, alreadyRunning, failed, notReady, items[] }` with each panel's own result, readiness, and blocker or error. After a Pane restart, or once an agent exits back to its shell, stopped panels report `running: false` (`initialized` stays true while the shell lives) in `panels list`, `panels screen`, and `panels wait` (which returns `stopped: true` instead of timing out), and `nextCommand` points at `panels resume`. `runpane watch` reports them as `panel.stopped` (`STOPPED ...`) when `--kinds` lists that kind; other consumers and `workspace state` see `agent.unknown` with `running: false`, never `agent.ready`.\",\n \"`runpane panels pause --panel <panel-id>`, `panes pause --pane <pane-id>` and `sessions pause --session <id|name>` pause a tab, a Pane, or a Session's chat and Panes: Pane stops each terminal tab the way quitting does, without asking, keeps its agent's conversation, and marks it paused. A chat tab can't pause yet: `panels pause --panel` on one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE`, and `panes pause` and `sessions pause` leave its agent running and count it in `chatTabsNotPaused`. A paused tab stays stopped through clicks, app restarts and updates until `panels resume --panel`, `panes resume` or `sessions resume` restarts it in its saved conversation. Each returns `{ action, scope, panelIds, unchanged, failed, chatTabsNotPaused, message, nextCommand }`. `panes list` reports a Pane's `paused` mark (`{ at, by: \\\"manual\\\" | \\\"auto\\\", idleHours? }`) and `inactiveSince`, the latest terminal output or input in its tabs or the last time the person selected it; `panels list` reports each tab's `paused`. `runpane watch` reports `panel.paused` (`PAUSED ...`) and `panel.unpaused` (`UNPAUSED ...`) under `--follow`, to a Session-scoped watch, or when `--kinds` lists them.\",\n \"`runpane panels last-message --panel <panel-id>` reads a Claude or Codex agent's last reply from its transcript (up to `--limit` characters, default 20,000, keeping the end and reporting `truncated`). It never scrapes the screen: without a transcript it prints `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and exits 1.\",\n \"`runpane panels conversation --conversation <id> [--source startup|resume|clear|compact]` records which conversation a terminal panel's agent is on (daemon action `runpane:panels:conversation`, request `{ panelId, paneId?, conversationId, source? }`, result `{ ok: true, paneId, panelId, conversationId, source?, sure }`; `sure` is true when the report came since the panel's agent started). It is not an `agent.report` event. The panel comes from `--panel` (with an optional `--pane`), else `PANE_PANEL_ID` and `PANE_SESSION_ID`, as for `runpane report`. `runpane panels conversation --hook` is the command for a Claude Code `SessionStart` hook: it reads the hook's JSON from stdin (`session_id`, `source`; at most 64 KiB, for at most 2 seconds), reports it for `PANE_PANEL_ID`, prints nothing on stdout or stderr, and always exits 0: outside Pane, on bad input, when Pane is not running, and with a Pane that predates the action. Without `--hook` it prints `Recorded conversation <id> for panel <panel-id>.` and fails like any other command.\",\n \"Commands with a contract `daemonAction` (the `panes` git, script, restore, and move commands, `folders list|create`, and `links open`) call the same Pane daemon channel as the matching button in the app and print `{ ok, data, error }`. Destructive ones add a pane:// `link` to review the Pane.\",\n \"`runpane links create` builds `pane://open?...` links; opening one in Pane selects what it names and never changes Pane state.\",\n \"`runpane docs search|read` search and read Pane docs, help, and installed Pane Chat skills offline. They ship in the npm package and the Pane app only.\",\n \"`runpane handoff` hands a task to another agent on this or another of your machines. The sending agent writes the note from `runpane handoff --template` (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state); `runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md` checks every section is filled in, requires the branch to be pushed (or pushes it with `--push`, never forcing), writes the note to that machine's `~/.pane/handoffs/` and starts the agent in a new Pane branched from the sender's branch. When the sender is a Pane tab running a Claude, Codex, Cursor or OpenCode session and the receiver is the same agent, the handoff carries that session: on another machine its files are copied into that machine's agent store and the receiver resumes the same id; on this machine the receiver resumes a fork, because two agents never resume one session on one machine. The receiver starts with no prompt, and the CLI runs the continuity check and reports JSON `continuity.result` `continued`, `inconclusive` (exit 0; `next` gives a probe question) or `not-continued` (exit 1; the receiver is kept and `next` gives the `--note-only` command). `--note-only`, a different agent, or a tab with no carried session starts a fresh agent with the note (`fresh-agent`). JSON `continuity` is `{ carried, result, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }`: `sessionId` is the sender's session, `resumedId` the id the receiver resumes (the fork's on this machine), `checks` are the continuity checks (`historyLoaded` is null when there was nothing to compare; `originalUntouched` is set for forks), and a dry run sets `carried`, `sessionId`, `fork`, `files` and `bytes` with no `result`. After `continued` or `inconclusive` the sending agent ends its turn and makes no more changes on the branch. An OpenCode session travels as its export: `opencode export` on this machine, then `opencode import` inside the receiving repository there, and an OpenCode receiver is accepted only to continue an OpenCode session. A Cursor session is carried only to another machine, best effort since Cursor's store is undocumented: Cursor has no fork, and a copied chat still writes to the original, so on this machine the result is `not-continued` and nothing is started. A destination's agent store is assumed at its default location (`~/.claude`, `~/.codex`, `~/.cursor`); a copy already there is replaced only when the sender's holds all of it and no agent there runs it, otherwise nothing is copied and the result is `not-continued`. A destination Pane too old to resume a carried session is refused before anything is sent (`ERR_HANDOFF_RESUME_UNSUPPORTED`). From a Session's terminal it skips git and creates a Session with the same name, goal and agent on the other machine instead; a same-agent Session resumes the sender's conversation when first opened. The receiver reports back to the sender's panel.\",\n \"`runpane pr watch`: Register durable GitHub PR monitoring for a Pane.\",\n \"`runpane pr unwatch`: Remove a Pane’s explicit PR registration.\",\n \"`runpane pr list`: List PR registrations and stopped watches.\",\n \"`runpane autopilot status|waiting|stack` read Autopilot (experimental): its switch, run and stack, and its queue: every approval or question waiting on the person on every machine it can reach, grouped by Session, plus `offline` and `cantSee` machines. `runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next]` puts one entry on its stack; relative paths resolve against the caller's directory, and a new entry never replaces the one on screen. `runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) [--note <text>] --because <why>`, `runpane autopilot close <id> --reason <why>` and `runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>]` act on an item, on the machine that holds it, recorded \\\"by Autopilot\\\" on every device; the person can Override an answer and reopen a close. Only Autopilot may run them (`AUTOPILOT_ONLY`), and they refuse a permission prompt with `AUTOPILOT_CANNOT_ANSWER`. An item someone else already answered is refused with who did.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes ...`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. `agent-context` and `version` accept and ignore `--pane-dir`, so one `--pane-dir` can be passed to every runpane command. In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, use Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane`), which reaches the Windows named-pipe daemon through WSL interop; older Pane builds need `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'`.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers emit best-effort telemetry to `https://runpane.com/api/runpane/telemetry` for command failures, download request/success/failure, and GitHub fallback usage. Routine command starts and successes emit nothing. A command failure is a thrown error, or a non-zero exit from `install` or `update` (also when setup runs them); other exit codes are documented results (such as `panels wait` not ready or `doctor` findings) and emit nothing. Polling and status commands (`panes list`, `panels wait`, `watch`, `panes archive`, `workspace state`, `agents status`, `doctor`) emit no failure events, and neither does a `not_found` failure outside `install` and `update`. A failure event carries the command, `failure_stage`, `failure_category`, `exit_code` when known, and `error_code` (the code printed in the `--json` error). Both wrappers share a per-install limit in `<PANE_DIR>/runpane-wrapper-telemetry.json`: one failure event per cause per hour and at most 10 per hour.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so installs reporting failures or downloads can be counted with `count(DISTINCT properties.install_id)`; this is not a count of all CLI users.\",\n \"Wrapper telemetry must be disabled in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `TF_BUILD`, `JENKINS_URL`, `TEAMCITY_VERSION` or `CODEBUILD_BUILD_ID` set), when `DO_NOT_TRACK=1` or `RUNPANE_TELEMETRY_DISABLED` is set, and when the local config explicitly disables analytics. It must not include raw paths, labels, prompts, raw error text, or environment values.\",\n \"Desktop local control reports useful failures as `runpane_local_control_failed`; normal polling, wait deadlines, and optional agent unavailability emit nothing. Reports contain coarse operation and failure classification only, with a five-minute deduplication interval and a maximum of 20 per hour per app process. Handled CLI failures are ordinary events, not exceptions.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"doctor\",\n \"--report\",\n \"--title\",\n \"watch failed\",\n \"--body-file\",\n \"/tmp/watch-evidence.txt\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"monitor\",\n \"--from\",\n \"earliest\",\n \"--follow\",\n \"--heartbeat\",\n \"60s\",\n \"--idle-after\",\n \"600000\",\n \"--ready-settle\",\n \"180000\",\n \"--blocked-settle\",\n \"30000\",\n \"--min-interval\",\n \"600000\",\n \"--expire-after\",\n \"10800000\",\n \"--exclude-pane\",\n \"pane-old\",\n \"--kinds\",\n \"agent.ready,agent.idle\",\n \"--include-held-input\",\n \"--self-test\"\n ],\n [\n \"daemon\",\n \"repair\",\n \"--pane-dir\",\n \"/tmp/pane-remote\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"cost\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--pinned\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"pin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"unpin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"rename\",\n \"--pane\",\n \"session-1\",\n \"--name\",\n \"issue-393\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"focus\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"user\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"list\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"get\",\n \"--session\",\n \"demo\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"update\",\n \"--session\",\n \"demo\",\n \"--from-json\",\n \"-\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel=panel-1\",\n \"--text=- [ ] item\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"commit\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"fix: title\\n\\nbody\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"links\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--panel\",\n \"panel-1\",\n \"--json\"\n ],\n [\n \"links\",\n \"open\",\n \"--url=pane://open?pane=pane-1\",\n \"--yes\"\n ],\n [\n \"mcp\",\n \"--toolsets\",\n \"core,git\",\n \"--read-only\"\n ],\n [\n \"docs\",\n \"search\",\n \"--query\",\n \"archive a pane\",\n \"--limit\",\n \"3\"\n ],\n [\n \"docs\",\n \"read\",\n \"--doc\",\n \"docs/PANE_MCP.md\"\n ],\n [\n \"agents\",\n \"start\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"fix-login\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Fix the login redirect\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agents\",\n \"send\",\n \"--pane\",\n \"pane-1\",\n \"--text=- [ ] item\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--keys\",\n \"down,enter\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"squash-rebase\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"feat: land it\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"folders\",\n \"create\",\n \"--repo\",\n \"3\",\n \"--name\",\n \"Reviews\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"move\",\n \"--pane\",\n \"pane-1\",\n \"--folder\",\n \"folder-9\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"watch\",\n \"--follow\"\n ],\n [\n \"watch\",\n \"--session\",\n \"__orchestration_session_1b4e28ba-2fa1-11d2-883f-0016d3cca427__\",\n \"--follow\",\n \"--json\",\n \"--kinds\",\n \"agent.ready,agent.blocked,pane.associated,pane.detached,pr.conflicted,pr.checks,pr.merged\",\n \"--ready-settle\",\n \"180000\"\n ],\n [\n \"watch\",\n \"--session=Release train\",\n \"--as\",\n \"release-watch\",\n \"--exclude-pane\",\n \"pane-old\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\",\n \"--pane-dir=/tmp/pane\"\n ],\n [\n \"version\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--agent\",\n \"claude\",\n \"--base-branch\",\n \"origin/release\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--tool-command\",\n \"wrapper codex-run\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"farm\",\n \"--tool-command\",\n \"agent-farm run free-range\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Plan\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"long\",\n \"--agent\",\n \"claude\",\n \"--initial-input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"ready\",\n \"--pr\",\n \"747\",\n \"--head\",\n \"FC5DCE9\",\n \"--summary\",\n \"Done: tests pass\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"blocked\",\n \"--question\",\n \"Which API version?\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--summary-file\",\n \"-\"\n ],\n [\n \"report\",\n \"--state=done\",\n \"--summary-file=/tmp/report.md\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--all-stopped\",\n \"--wait-ready\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"last-message\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"5000\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation\",\n \"0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44\",\n \"--source\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation=abc\",\n \"--source=compact\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--hook\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--base\",\n \"release/foo\",\n \"--branch\",\n \"agents/w5a\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"prompt.md\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5b\",\n \"--base-branch\",\n \"origin/main\",\n \"--branch\",\n \"agents/w5b\",\n \"--worktree-name\",\n \"w5b-dir\",\n \"--tool-command\",\n \"bash\",\n \"--initial-input-file\",\n \"-\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--remove-worktree\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--remove-worktree\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"testing-account\",\n \"--ttl\",\n \"30m\",\n \"--wait\",\n \"1800000\",\n \"--note\",\n \"call QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"staging-db\",\n \"--ttl\",\n \"1500\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name=db\",\n \"--ttl=2h\"\n ],\n [\n \"lock\",\n \"release\",\n \"--name\",\n \"testing-account\",\n \"--force\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"list\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"--help\"\n ],\n [\n \"panels\",\n \"open\",\n \"--pane\",\n \"session-1\",\n \"--file\",\n \"plan.html\",\n \"--title\",\n \"Plan\",\n \"--split\",\n \"--no-focus\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"open\",\n \"--url\",\n \"http://localhost:3000\",\n \"--tab\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"correction\",\n \"--interrupt\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"repos\",\n \"get\",\n \"--repo\",\n \"Pane\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--build-script\",\n \"pnpm install\",\n \"--run-script\",\n \"pnpm dev\",\n \"--worktree-folder\",\n \"../wt\",\n \"--clear\",\n \"archive-script,system-prompt\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"orch\",\n \"--until-event\",\n \"--json\",\n \"--kinds\",\n \"agent.report,agent.blocked,panel.exited,pane.gone\",\n \"--timeout-ms\",\n \"600000\"\n ],\n [\n \"pr\",\n \"watch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"unwatch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"list\",\n \"--pane\",\n \"worker\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"0\"\n ],\n [\n \"watch\",\n \"--until-event\",\n \"--timeout-ms\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--timeout-ms\",\n \"off\"\n ],\n [\n \"watch\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"off\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--blocked-settle\",\n \"2s\",\n \"--min-interval\",\n \"1m\",\n \"--idle-after\",\n \"20m\",\n \"--expire-after\",\n \"off\",\n \"--heartbeat\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"go\",\n \"--expect-report\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"claude\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"codex\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"inbox\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"in-5e6f7a8b\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"--all\",\n \"--session\",\n \"Release\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"fyi\",\n \"--no-expect-report\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"prompt.md\",\n \"--as-file-pointer\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Red\",\n \"--wait\",\n \"--timeout\",\n \"30m\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"task.md\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"deploy\",\n \"--wait\",\n \"5000\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"nightly\",\n \"--cron\",\n \"0 3 * * *\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Sweep bugs\",\n \"--worktree-name\",\n \"nightly-sweep\",\n \"--paused\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo=2\",\n \"--name=deps\",\n \"--cron=0 9 * * 1\",\n \"--tool-command\",\n \"./scripts/nightly-report.sh\",\n \"--prompt-file\",\n \"-\"\n ],\n [\n \"loops\",\n \"create\",\n \"--session\",\n \"new\",\n \"--name\",\n \"triage\",\n \"--cron\",\n \"0 9 * * 1-5\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Triage\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--cron\",\n \"0 4 * * *\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--session\",\n \"Nightly\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"run\",\n \"--loop=loop-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"pause\",\n \"--loop\",\n \"loop-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"loops\",\n \"list\",\n \"--json\"\n ],\n [\n \"loops\",\n \"--help\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Ship it?\",\n \"--context\",\n \"Tests pass.\",\n \"--choices=[{\\\"label\\\":\\\"Yes\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"No\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--ui=approve\",\n \"--title=Drop the table?\",\n \"--detail=DROP TABLE t;\",\n \"--detail-label=Show the SQL\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which branch?\",\n \"--suggestions=[\\\"main\\\"]\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"info\",\n \"--title\",\n \"Done\",\n \"--action={\\\"label\\\":\\\"Open\\\",\\\"url\\\":\\\"https://example.com\\\"}\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"approve\",\n \"--title\",\n \"Ship the notes?\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR\\\",\\\"url\\\":\\\"https://example.com/pr/1\\\"}]\"\n ],\n [\n \"notify\",\n \"--resume\",\n \"n-1\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"list\",\n \"--project\",\n \"stripe\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"set\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\"\n ],\n [\n \"secrets\",\n \"delete\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"stripe\",\n \"--only\",\n \"A,B\",\n \"--env\",\n \"KEY=STRIPE_SECRET_KEY\",\n \"--env=TOKEN=GH_TOKEN\",\n \"--file\",\n \"CERT\",\n \"--\",\n \"node\",\n \"-e\",\n \"run()\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"qa\",\n \"sh\",\n \"-c\",\n \"echo ok\"\n ],\n [\n \"secrets\",\n \"request\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--title\",\n \"Stripe test key\",\n \"--reason\",\n \"- for the webhook tests\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--env\",\n \"[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"x\\\"}]\",\n \"--env-remove\",\n \"OLD,STALE\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"session-a\",\n \"--merged\",\n \"--closed\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"status\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"waiting\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--item\",\n \"n-1\",\n \"--note\",\n \"Needs a go\",\n \"--next\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--file\",\n \"/tmp/plan.html\",\n \"--title\",\n \"Plan\",\n \"--tab\",\n \"https://localhost:5173\",\n \"--tab=/tmp/notes.md\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--session\",\n \"Release\"\n ],\n [\n \"watch\",\n \"--as\",\n \"autopilot\",\n \"--kinds\",\n \"item.created,item.answered,session.created,session.archived,orchestrator.turn_ended\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"--decision\",\n \"D-147\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--relays\",\n \"in-1a2b3c4d\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Q-39: Run it?\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which?\",\n \"--about-session=Release\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--choice\",\n \"Yes\",\n \"--note\",\n \"Go\",\n \"--because\",\n \"D-22\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--approve\",\n \"--because=The person said so\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--deny\",\n \"--because\",\n \"D-23\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--text\",\n \"main\",\n \"--because\",\n \"D-24\"\n ],\n [\n \"autopilot\",\n \"close\",\n \"n-1\",\n \"--reason\",\n \"The PR merged\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"rewrite\",\n \"n-1\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Q-3: Which?\",\n \"--context\",\n \"Why\",\n \"--choices=[{\\\"label\\\":\\\"A\\\"}]\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"url\\\":\\\"https://example.com\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"answer\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took=n-1\",\n \"question\",\n \"--status\",\n \"Checking the logs\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"--took-as\",\n \"answer\"\n ],\n [\n \"notify\",\n \"--update\",\n \"Q-20\",\n \"--asked\",\n \"Can it be undone?\",\n \"--what-changed\",\n \"- Added: it can be undone\",\n \"--questions\",\n \"[{\\\"title\\\":\\\"A\\\",\\\"choices\\\":[{\\\"label\\\":\\\"x\\\"}]},{\\\"title\\\":\\\"B\\\",\\\"choices\\\":[{\\\"label\\\":\\\"y\\\"}]}]\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane repos rename\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\",\n \"runpane loops list\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\",\n \"python -m runpane loops list\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"description\": \"A failed command under --json, printed on stdout with a non-zero exit. The same message, plus a Next line, goes to stderr.\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\",\n \"description\": \"What happened, why, and what was or was not changed.\"\n },\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable identifier to branch on, such as ENOENT or ERR_UNKNOWN_CHANNEL. ERR_RUNPANE_FAILED when the failure has no specific code.\"\n },\n \"next\": {\n \"type\": \"string\",\n \"description\": \"The step to take next, when one is known.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteDaemonService\",\n \"remoteSetup\",\n \"watchDefaults\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteDaemonService\": {\n \"type\": \"object\",\n \"required\": [\n \"paneDir\",\n \"managed\",\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"managed\": {\n \"type\": \"boolean\"\n },\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"executableHealth\": {\n \"type\": \"object\",\n \"required\": [\n \"processImage\",\n \"restart\",\n \"checkedAt\"\n ],\n \"properties\": {\n \"processImage\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"runtimePath\",\n \"installedPath\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"current\",\n \"replaced\",\n \"deleted\",\n \"unknown\"\n ]\n },\n \"runtimePath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"installedPath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"restart\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"ready\",\n \"broken\",\n \"unknown\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"resolvedPath\": {\n \"type\": \"string\"\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"diagnosticCode\": {\n \"enum\": [\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n },\n \"checkedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\",\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"watchDefaults\": {\n \"type\": \"object\",\n \"required\": [\n \"follow\",\n \"untilEvent\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"follow\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"summary\",\n \"kinds\",\n \"readySettle\",\n \"blockedSettle\",\n \"minInterval\",\n \"idleAfter\",\n \"idleBackoff\",\n \"expireAfter\",\n \"heartbeat\",\n \"includeShells\",\n \"heldInputDetection\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"readySettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"blockedSettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"minInterval\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"expireAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"heartbeat\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"includeShells\": {\n \"type\": \"boolean\"\n },\n \"heldInputDetection\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"untilEvent\": {\n \"type\": \"object\",\n \"required\": [\n \"summary\",\n \"kinds\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n },\n \"cliInstalls\": {\n \"type\": \"object\",\n \"required\": [\n \"installs\",\n \"warnings\"\n ],\n \"properties\": {\n \"managedVersion\": {\n \"type\": \"string\"\n },\n \"installs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"origin\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"origin\": {\n \"enum\": [\n \"pane\",\n \"npm\",\n \"other\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"npmPrefix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"path\",\n \"message\",\n \"fix\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"RUNPANE_STALE_INSTALL\",\n \"RUNPANE_STALE_UNMANAGED\",\n \"RUNPANE_STALE_MANAGED\",\n \"RUNPANE_MANAGED_BEHIND\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"fixed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"ok\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"workspacesSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"machine\",\n \"otherMachines\",\n \"lines\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"This machine's Tailscale name.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"description\": \"Why workspaces are off.\"\n },\n \"fix\": {\n \"type\": \"string\",\n \"description\": \"The one step that turns them on.\"\n },\n \"otherMachines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"os\",\n \"online\",\n \"owner\",\n \"mine\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own Tailscale login.\"\n }\n }\n }\n },\n \"lines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The block printed by help, doctor, and agent-context.\"\n }\n }\n },\n \"daemonRepairResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"paneDir\",\n \"strategy\",\n \"launcherPath\",\n \"before\",\n \"after\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"systemd-user\",\n \"launch-agent\",\n \"scheduled-task\",\n \"manual\",\n \"skipped\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"before\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"after\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"scriptSource\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"source\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"scriptSetting\": {\n \"type\": \"object\",\n \"required\": [\n \"value\",\n \"fallback\",\n \"effective\"\n ],\n \"properties\": {\n \"value\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"fallback\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"effective\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"settings\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"currentBranch\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"configFile\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"envVars\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"currentBranch\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"configFile\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"buildScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"runScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"archiveScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"envVars\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"repoUpdateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n },\n \"settings\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"buildScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"runScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"archiveScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"envVars\": {\n \"type\": \"object\",\n \"description\": \"set adds or replaces variables by key; remove deletes them by key.\",\n \"minProperties\": 1,\n \"properties\": {\n \"set\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n },\n \"remove\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoUpdateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"changed\": {\n \"type\": \"array\",\n \"items\": {\n \"enum\": [\n \"name\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"envVars\"\n ]\n }\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"name\"\n ],\n \"properties\": {\n \"repo\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"updatedAt\",\n \"machine\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"type\": \"string\",\n \"description\": \"ISO time it was last set.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine where it was last set.\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"secrets\",\n \"storage\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"secrets\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"storage\": {\n \"type\": \"object\",\n \"description\": \"Where this machine keeps values. encrypted is false only for the file store on a machine without an OS keyring.\",\n \"required\": [\n \"kind\",\n \"encrypted\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"keychain\",\n \"file\"\n ]\n },\n \"encrypted\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetRequest\": {\n \"description\": \"What `runpane secrets set` sends on runpane:secrets:set. value comes from stdin.\",\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"value\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"value\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"replaced\",\n \"secret\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"replaced\": {\n \"type\": \"boolean\"\n },\n \"secret\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"projectEnvVar\": {\n \"description\": \"A project environment variable. Plain values are shown in full; keychain and Pane secret variables are references, never values.\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"key\",\n \"source\",\n \"value\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"plain\"\n },\n \"value\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"An existing macOS keychain generic-password item.\",\n \"required\": [\n \"key\",\n \"source\",\n \"service\",\n \"account\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"keychain\"\n },\n \"service\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"account\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The Pane secret project/name.\",\n \"required\": [\n \"key\",\n \"source\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"secret\"\n },\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"branch\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\",\n \"cursor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"associateSession\": {\n \"type\": \"string\",\n \"description\": \"Session id to associate each created Pane with; the CLI sets it from PANE_ORCHESTRATION_SESSION_ID unless --no-associate.\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"staged\": {\n \"type\": \"boolean\"\n },\n \"attempts\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"association\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"ok\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"setupQueue\": {\n \"type\": \"object\",\n \"required\": [\n \"position\",\n \"message\"\n ],\n \"properties\": {\n \"position\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"required\": [\n \"ref\",\n \"refresh\"\n ],\n \"properties\": {\n \"ref\": {\n \"type\": \"string\"\n },\n \"commit\": {\n \"type\": \"string\"\n },\n \"refresh\": {\n \"type\": \"string\",\n \"enum\": [\n \"fetched\",\n \"recent\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"notice\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\",\n \"pinned\",\n \"agentStatus\",\n \"ownership\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"inactiveSince\": {\n \"type\": \"string\",\n \"description\": \"When the Pane was last used: the latest terminal output or input in its tabs, or the last time the person selected it (ISO 8601).\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\",\n \"description\": \"Set while every terminal tab of the Pane is paused: the latest tab's mark.\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"agentStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"agentState\": {\n \"enum\": [\n \"ready\",\n \"working\",\n \"blocked\",\n \"none\"\n ]\n },\n \"ownership\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostResult\": {\n \"$defs\": {\n \"usageTotals\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"usageByModel\": {\n \"type\": \"object\",\n \"required\": [\n \"model\",\n \"provider\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"model\": {\n \"type\": \"string\"\n },\n \"provider\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostSlice\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"pane\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"paneName\",\n \"worktreePath\",\n \"repoId\",\n \"archived\",\n \"createdAtMs\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"fromMs\",\n \"toMs\",\n \"pricingAsOf\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"fromMs\": {\n \"type\": \"number\"\n },\n \"toMs\": {\n \"type\": \"number\"\n },\n \"pricingAsOf\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/pane\"\n }\n },\n \"unattributed\": {\n \"$ref\": \"#/$defs/paneCostSlice\"\n },\n \"totals\": {\n \"$ref\": \"#/$defs/usageTotals\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"gen\",\n \"at\",\n \"kind\",\n \"paneId\",\n \"paneName\",\n \"source\"\n ],\n \"properties\": {\n \"gen\": {\n \"type\": \"number\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"agent.ready\",\n \"agent.busy\",\n \"agent.blocked\",\n \"agent.unknown\",\n \"agent.idle\",\n \"pane.created\",\n \"pane.gone\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"agent.report\",\n \"agent.report.missing\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\",\n \"item.created\",\n \"item.answered\",\n \"session.created\",\n \"session.archived\",\n \"orchestrator.turn_ended\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"to\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"source\": {\n \"enum\": [\n \"agent\",\n \"exit\",\n \"session\",\n \"github\"\n ]\n },\n \"reason\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"settledMs\": {\n \"type\": \"number\"\n },\n \"idleMs\": {\n \"type\": \"number\"\n },\n \"idleCount\": {\n \"type\": \"number\"\n },\n \"heldInput\": {\n \"type\": \"string\"\n },\n \"heldInputPresent\": {\n \"type\": \"boolean\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Baseline agent entries: whether the panel's terminal process is live. A stopped panel is never agent.ready.\"\n },\n \"baseline\": {\n \"const\": true\n },\n \"replay\": {\n \"const\": true,\n \"description\": \"Set on baseline entries delivered after a reset. A replayed entry restates current state and is never a new transition: a replayed agent.ready is not READY.\"\n },\n \"changedWhileAway\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Named Session of a pane.associated or pane.detached entry.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"description\": \"PR of a pr.conflicted, pr.checks, or pr.merged entry.\",\n \"required\": [\n \"number\",\n \"url\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"description\": \"Base branch of a pr.base_changed (old to new) or pr.base_deleted (to is the PR's current base name) entry.\",\n \"required\": [\n \"from\",\n \"to\"\n ],\n \"properties\": {\n \"from\": {\n \"type\": \"string\"\n },\n \"to\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"checks\": {\n \"enum\": [\n \"passed\",\n \"failed\"\n ],\n \"description\": \"Failures are immediate; success requires complete nonempty required checks on the pinned head.\"\n },\n \"failingChecks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Newly failing check names on this head.\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\",\n \"description\": \"Report of an agent.report entry; its summary is cut to 2,000 characters (the panel keeps up to 16,000).\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Added to watch --json NDJSON events. Persist with gen after processing for numeric rearm.\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentState\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"running\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"lastOutput\": {\n \"type\": \"string\",\n \"description\": \"What an agent.report.missing entry's agent said last, up to 2,000 characters (the end is kept).\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ],\n \"description\": \"transcript: the agent's last reply; screen: the last lines on its screen.\"\n },\n \"expectedSince\": {\n \"type\": \"string\",\n \"description\": \"When the work an agent.report.missing entry is about was sent.\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The screen lines of the prompt an agent.blocked entry's agent waits on (at most 20).\"\n },\n \"itemId\": {\n \"type\": \"string\",\n \"description\": \"The Notify item of an item.created or item.answered entry; `runpane autopilot stack push --item <id>` takes it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentReport\": {\n \"type\": \"object\",\n \"description\": \"The latest runpane report from an agent panel.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelReport\": {\n \"type\": \"object\",\n \"description\": \"A panel's latest runpane report in sessions overview. data is the summary when it is a complete JSON object, returned as the worker gave it; a plain-text, array, scalar, invalid, more than 64 levels deep, or truncated summary has no data.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n },\n \"data\": {\n \"type\": \"object\",\n \"description\": \"The worker's JSON object summary, returned as given. Pane never reads, renames or validates its fields.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"since\": {\n \"type\": \"number\"\n },\n \"as\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"now\",\n \"earliest\"\n ]\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry/properties/kind\"\n }\n },\n \"paneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Named Session id or exact name. Limits the wait to the Session's associated Panes, resolved on every read, and implies the opt-in pane.associated, pane.detached, and pr.* entries. Cannot be combined with paneIds.\"\n },\n \"excludePaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"nameContains\": {\n \"type\": \"string\"\n },\n \"agentsOnly\": {\n \"type\": \"boolean\"\n },\n \"ackNow\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInput\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInputPresence\": {\n \"type\": \"boolean\"\n },\n \"idleAfterMs\": {\n \"type\": \"number\"\n },\n \"idleWindowStartMs\": {\n \"type\": \"number\"\n },\n \"settleMs\": {\n \"type\": \"number\"\n },\n \"blockedSettleMs\": {\n \"type\": \"number\"\n },\n \"minIntervalMs\": {\n \"type\": \"number\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Expected epoch for since. Requires since and cannot be combined with as. Mismatch returns epoch-changed reset.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceStateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\",\n \"timedOut\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"dropped\": {\n \"type\": \"number\"\n },\n \"reset\": {\n \"type\": \"object\",\n \"required\": [\n \"reason\"\n ],\n \"properties\": {\n \"reason\": {\n \"enum\": [\n \"first-use\",\n \"epoch-changed\",\n \"cursor-truncated\",\n \"unknown-consumer\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"session\": {\n \"type\": \"object\",\n \"description\": \"The Session a session request resolved to. A client that sent session and gets no session back is talking to a daemon that ignored it.\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"activityGeneration\": {\n \"type\": \"number\",\n \"description\": \"Latest meaningful activity in scope, including events hidden by output kinds or cadence. Natural expiry resets when it advances.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"favoritePinnedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"pane\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"pane\": {\n \"$ref\": \"#/jsonSchemas/paneListResult/properties/panes/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"focused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"focused\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ],\n \"description\": \"`completed`: the worktree is gone from its path and from git (see trashDeletion). `failed`: removal failed and the worktree may remain; ok is false. `timeout`: the archive script or git removal is still running in the background; ok stays true. `not-applicable`: nothing was removed (main-repo Pane, or an adopted worktree without --remove-worktree). Released CLIs decode exactly these values.\"\n },\n \"trashDeletion\": {\n \"enum\": [\n \"pending\",\n \"done\"\n ],\n \"description\": \"Present with worktreeCleanup `completed`. `pending`: the removed worktree was moved into the repository's .git/pane-trash and its files are still being deleted in the background. `done`: the files are gone.\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"upstreamGone\": {\n \"type\": \"boolean\",\n \"description\": \"The branch had an upstream that no longer exists on the remote, so commits were compared with the base branch.\"\n },\n \"mergedViaPr\": {\n \"type\": \"object\",\n \"description\": \"A merged pull request whose head commit is HEAD. Its commits are not counted as unpushed.\",\n \"required\": [\n \"number\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"busyAgents\": {\n \"type\": \"array\",\n \"description\": \"Agents in the Pane that are mid-turn (working) or waiting for an answer (blocked). Archiving would stop them, so archive refuses with agent-busy unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"state\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"enum\": [\n \"working\",\n \"blocked\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"unsentInput\": {\n \"type\": \"array\",\n \"description\": \"Tabs holding input a person typed that archiving would lose: an agent composer's unsent text or an editor's unsaved edits. Archive refuses with unsent-input unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"kind\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"composer\",\n \"editor\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"dryRun\",\n \"wouldArchive\",\n \"forced\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"wouldArchive\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/2/properties/blocked\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"busyAgents\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/busyAgents\"\n },\n \"unsentInput\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/unsentInput\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"dock\": {\n \"type\": \"boolean\",\n \"description\": \"True on the Pane's terminal dock (its plain shell); omitted on every other panel.\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"resumeId\": {\n \"type\": \"string\"\n },\n \"resumeSource\": {\n \"enum\": [\n \"agent-farm\",\n \"configured\",\n \"agent\"\n ]\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n },\n \"openedBy\": {\n \"type\": \"object\",\n \"description\": \"Who opened this tab through runpane, and the tab that opened it when known. Omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose source is agent.\",\n \"required\": [\n \"source\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"agent\",\n \"user\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\",\n \"composer\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"composer\": {\n \"type\": \"object\",\n \"required\": [\n \"isPresent\",\n \"hasUndeliveredText\"\n ],\n \"properties\": {\n \"isPresent\": {\n \"type\": \"boolean\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"ghostText\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"asFilePointer\": {\n \"type\": \"boolean\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"interrupt\": {\n \"type\": \"boolean\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n },\n \"operationId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"enum\": [\n \"cr\",\n \"tab\",\n \"none\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"acp\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"evidence\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"taken\",\n \"queued\",\n \"in-composer\",\n \"unknown\"\n ]\n },\n \"evidence\": {\n \"enum\": [\n \"transcript\",\n \"screen\",\n \"argv\",\n \"acp-receipt\"\n ]\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human explanation of delivery; queued means the agent has not read the message yet.\"\n }\n },\n \"additionalProperties\": false\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitComposerResult/properties/blocked\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"leading-bang-runs-shell\",\n \"leading-hash-memory\",\n \"leading-slash-command\",\n \"leading-at-mention\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n },\n \"operationId\": {\n \"type\": \"string\",\n \"description\": \"Durable host operation ID for Chat delivery reconciliation.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"stopped\": {\n \"const\": true,\n \"description\": \"The wait did not match and the panel's terminal is not running (ready and idle waits return this after a 2-second grace instead of timing out); nextCommand is `runpane panels resume`.\"\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"action\",\n \"message\",\n \"panel\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ],\n \"description\": \"resumed: the panel's terminal was restarted in place. already-running: nothing was restarted.\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\",\n \"description\": \"The agent conversation the panel resumes, when Pane knows it.\"\n },\n \"panel\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeManyResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"scope\",\n \"resumed\",\n \"alreadyRunning\",\n \"failed\",\n \"notReady\",\n \"concurrency\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"Every stopped panel in scope resumed and, with --wait-ready, became ready.\"\n },\n \"scope\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\",\n \"sessionName\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"all-stopped\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"resumed\": {\n \"type\": \"number\"\n },\n \"alreadyRunning\": {\n \"type\": \"number\",\n \"description\": \"Agent panels in scope that were already running and were left alone.\"\n },\n \"failed\": {\n \"type\": \"number\",\n \"description\": \"Panels that could not be restarted; their item carries error.\"\n },\n \"notReady\": {\n \"type\": \"number\",\n \"description\": \"With --wait-ready: resumed panels that were blocked or not ready within --ready-timeout-ms.\"\n },\n \"concurrency\": {\n \"type\": \"number\",\n \"description\": \"Panels restarted at once.\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"paneName\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why this panel could not be resumed; the others still were.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelLastMessageResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"agentType\",\n \"text\",\n \"length\",\n \"limit\",\n \"truncated\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"length\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"truncated\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"reason\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"reason\": {\n \"const\": \"transcript-unavailable\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelConversationRequest\": {\n \"type\": \"object\",\n \"description\": \"What runpane panels conversation sends the daemon (runpane:panels:conversation).\",\n \"required\": [\n \"panelId\",\n \"conversationId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"conversationId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 256\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelConversationResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"conversationId\",\n \"sure\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"conversationId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n },\n \"sure\": {\n \"type\": \"boolean\",\n \"description\": \"True when this report came since the panel's agent started, so Pane knows the panel is on this conversation.\"\n },\n \"generation\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"type\",\n \"title\",\n \"placement\",\n \"active\",\n \"reused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"enum\": [\n \"browser\",\n \"editor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"reused\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\",\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n \"force\": {\n \"type\": \"boolean\",\n \"description\": \"Close it as the tab's close button does, skipping every check but permanent: for a stuck tab, or one the person asked to close. A busy agent's turn and unsent input are lost.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"closed\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when the close was refused (the tab stays open); the CLI exits 1.\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\",\n \"description\": \"Omitted only when the tab was already gone.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"The tab's type, such as terminal, chat, browser or editor. Omitted only when the tab was already gone.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"closed\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\",\n \"description\": \"Closed with --force, skipping the checks that would have kept it.\"\n },\n \"alreadyClosed\": {\n \"type\": \"boolean\",\n \"description\": \"No tab has this id: it was closed already.\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"wouldClose\": {\n \"type\": \"boolean\",\n \"description\": \"Dry run only: whether the close would go ahead.\"\n },\n \"refused\": {\n \"type\": \"object\",\n \"description\": \"Why the tab was kept open. opened-by-user: not opened by an agent with --source agent (including tabs from before Pane recorded openers). permanent: a permanent tab. agent-busy: its agent is mid-turn or waiting for an answer. unsent-input: unsent composer text or unsaved editor edits. input-unknown: a live tab whose input Pane cannot read.\",\n \"required\": [\n \"code\",\n \"message\",\n \"next\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"opened-by-user\",\n \"permanent\",\n \"agent-busy\",\n \"unsent-input\",\n \"input-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"composer-unknown\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"sessionListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessions\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"selectedSessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"internalSessionId\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"object\",\n \"description\": \"Only when the update archived the Session: what happened to each associated Pane.\",\n \"required\": [\n \"archivedPaneIds\",\n \"keptPanes\",\n \"failedPanes\"\n ],\n \"properties\": {\n \"archivedPaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"keptPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"code\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"failedPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"error\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"sessionOverviewResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"status\",\n \"panes\",\n \"activity\",\n \"refreshedAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/panelReport\"\n }\n }\n }\n }\n }\n }\n },\n \"activity\": {\n \"type\": \"array\"\n },\n \"refreshedAt\": {\n \"type\": \"string\"\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"locks\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"scope\",\n \"owner\",\n \"acquiredAt\",\n \"expiresAt\",\n \"ttlMs\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"scope\": {\n \"enum\": [\n \"session\",\n \"global\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"acquiredAt\": {\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"ttlMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ttlMs\",\n \"owner\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"ttlMs\": {\n \"type\": \"number\"\n },\n \"waitMs\": {\n \"type\": \"number\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"renewed\",\n \"waitedMs\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acquired\": {\n \"const\": true\n },\n \"renewed\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"timedOut\",\n \"waitedMs\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"acquired\": {\n \"const\": false\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"lockReleaseResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"forced\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"released\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"reason\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"released\": {\n \"const\": false\n },\n \"reason\": {\n \"const\": \"not-owner\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"daemonActionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"data\": {},\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"link\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"linkCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"url\",\n \"target\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"target\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"repo\",\n \"session\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"docsSearchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"query\",\n \"results\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"query\": {\n \"type\": \"string\"\n },\n \"results\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"title\",\n \"kind\",\n \"excerpt\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"excerpt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"docsReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"path\",\n \"title\",\n \"kind\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentTaskResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"link\",\n \"ready\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"promptDelivered\": {\n \"type\": \"boolean\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"status\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"working\",\n \"ready\",\n \"blocked\",\n \"idle\",\n \"exited\",\n \"unknown\"\n ]\n },\n \"screen\": {\n \"type\": \"string\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentSendResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"delivered\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"blocked\": {\n \"type\": \"string\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextUnknownCommandError\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"code\",\n \"message\",\n \"candidates\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"code\": {\n \"const\": \"unknown_command\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"candidates\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"reportResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"report\",\n \"sessionIds\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"sessionIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"liveWatchers\": {\n \"type\": \"number\",\n \"description\": \"Watches that were waiting for this report when it was journaled and received it. A snapshot: a named-cursor watch that waits later still receives it. Absent from older daemons.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"merged\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"type\": \"boolean\",\n \"description\": \"Also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessionId\",\n \"merged\",\n \"removeWorktree\",\n \"archived\",\n \"skipped\",\n \"failed\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n },\n \"archived\": {\n \"type\": \"number\",\n \"description\": \"Panes archived, or that would be archived with --dry-run.\"\n },\n \"skipped\": {\n \"type\": \"number\"\n },\n \"failed\": {\n \"type\": \"number\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"outcome\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"outcome\": {\n \"enum\": [\n \"archived\",\n \"would-archive\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"skipped\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\",\n \"missing-pane\",\n \"already-archived\",\n \"main-repo\",\n \"pr-open\",\n \"pr-not-merged\",\n \"pr-status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"open\",\n \"merged\",\n \"closed\",\n \"none\",\n \"unknown\"\n ]\n },\n \"number\": {\n \"type\": \"number\"\n },\n \"reason\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"worktreeCleanup\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/worktreeCleanup\"\n },\n \"trashDeletion\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/trashDeletion\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"prWatchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"watch\": {\n \"type\": \"object\"\n },\n \"watches\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"removed\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"inboxListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"sessionId\",\n \"kind\",\n \"at\",\n \"paneId\",\n \"paneName\",\n \"panelId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"report\",\n \"report.missing\",\n \"blocked\"\n ]\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"answers\": {\n \"type\": \"array\",\n \"description\": \"The answers the Session's workers got, kept 7 days. Absent from an older Pane.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"answer\",\n \"by\",\n \"worker\",\n \"answeredAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"answer\": {\n \"type\": \"string\"\n },\n \"by\": {\n \"enum\": [\n \"you\",\n \"autopilot\",\n \"orchestrator\",\n \"pane\"\n ],\n \"description\": \"Who answered.\"\n },\n \"because\": {\n \"type\": \"string\"\n },\n \"worker\": {\n \"type\": \"string\",\n \"description\": \"The worker that asked.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"answeredAt\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"enum\": [\n \"call\",\n \"tab\"\n ],\n \"description\": \"How it reached the worker; absent while its tab is busy.\"\n },\n \"undelivered\": {\n \"type\": \"string\",\n \"description\": \"Why Pane can't type it into the worker's tab (its tab closed, or Pane can't see what runs there): pass it on yourself.\"\n },\n \"decision\": {\n \"type\": \"string\",\n \"description\": \"The D-n recorded for it; absent: run `runpane inbox ack <id> --decision <D-n>`.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"inboxAckResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acknowledged\",\n \"unknown\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acknowledged\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"unknown\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"dispatchWaitResult\": {\n \"type\": \"object\",\n \"description\": \"What `--wait` prints, as one line: how the dispatched work ended. Exit codes: 0 report, 3 missing, 4 blocked, 5 exited or gone, 124 timeout. Exit 6 is indeterminate after journal replacement or history loss; inspect the panel and inbox before retrying.\",\n \"required\": [\n \"ok\",\n \"outcome\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"true only for outcome report.\"\n },\n \"outcome\": {\n \"enum\": [\n \"report\",\n \"missing\",\n \"blocked\",\n \"exited\",\n \"gone\",\n \"timeout\",\n \"indeterminate\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\",\n \"description\": \"The journal entry that ended the wait (on timeout, the position reached).\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"nextCommand\": {\n \"type\": \"string\",\n \"description\": \"Recovery command when the outcome is indeterminate.\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopInput\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"cron\",\n \"enabled\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"loop\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"cron\",\n \"enabled\",\n \"createdAtMs\",\n \"nextRunAtMs\",\n \"scheduleText\",\n \"runs\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"nextRunAtMs\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"runs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"loopId\",\n \"slotAtMs\",\n \"trigger\",\n \"status\",\n \"error\",\n \"sessionId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"loopId\": {\n \"type\": \"string\"\n },\n \"slotAtMs\": {\n \"type\": \"number\"\n },\n \"trigger\": {\n \"enum\": [\n \"schedule\",\n \"manual\"\n ]\n },\n \"status\": {\n \"enum\": [\n \"started\",\n \"failed\",\n \"skipped\"\n ]\n },\n \"error\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"sessionId\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loops\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loops\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loop\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loop\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDryRunResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"dryRun\",\n \"input\",\n \"scheduleText\",\n \"nextRunAtMs\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"input\": {\n \"$ref\": \"#/jsonSchemas/loopInput\"\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"nextRunAtMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyChoice\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The answer notify returns when the person picks this choice.\"\n },\n \"detail\": {\n \"type\": \"string\",\n \"description\": \"One more line about this choice.\"\n },\n \"recommended\": {\n \"type\": \"boolean\",\n \"description\": \"Shade this choice as the one you recommend.\"\n },\n \"was\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"--update only: the label this choice had before it was reworded. A choice whose label stays but whose detail changed may repeat its label here.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifySubQuestion\": {\n \"description\": \"One question of an options approval that asks several at once; the person picks one choice.\",\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"choices\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"notifyAction\": {\n \"type\": \"object\",\n \"required\": [\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"url\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"notifyLink\": {\n \"description\": \"A related file, page or approval shown as a chip on the card: a label and exactly one of path, url or approval.\",\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"minProperties\": 2,\n \"maxProperties\": 2,\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 80,\n \"description\": \"The chip's text.\"\n },\n \"path\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A file on the asking machine; a relative path resolves against the caller's working directory.\"\n },\n \"url\": {\n \"type\": \"string\",\n \"pattern\": \"^https?://\\\\S+$\",\n \"description\": \"An http or https page.\"\n },\n \"approval\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Another approval, by its id or Q-n; it opens in place, with Back.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyRequest\": {\n \"description\": \"What `runpane notify` and `runpane secrets request` send on the runpane:notify daemon channel: the question fields, with relays or aboutSession when they apply; resume alone; took with tookAs (and status); or update with asked, whatChanged and the changed fields. Only `secrets request` sends ui secret.\",\n \"type\": \"object\",\n \"required\": [\n \"caller\"\n ],\n \"properties\": {\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"secret\",\n \"info\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"questions\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n },\n \"description\": \"ui options, instead of choices: several questions, one pick each.\"\n },\n \"detail\": {\n \"type\": \"string\"\n },\n \"detailLabel\": {\n \"type\": \"string\",\n \"maxLength\": 40\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"action\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n },\n \"secret\": {\n \"type\": \"object\",\n \"description\": \"ui secret only: the Pane secret the answer is saved as.\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n },\n \"resume\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"took\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) whose reply the reader took; sent with tookAs and nothing but status.\"\n },\n \"tookAs\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ],\n \"description\": \"answer records the reply as the answer; question shows the reader is getting more context.\"\n },\n \"status\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"tookAs question: what the reader is doing, shown on the approval.\"\n },\n \"update\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) to save a new version of; the question fields sent replace those fields. No ui.\"\n },\n \"asked\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: the person's question, as they typed it.\"\n },\n \"whatChanged\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: what changed, one line per change.\"\n },\n \"relays\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A waiting item (an id from `runpane inbox`) to improve instead of posting a second card. ui and title are still required.\"\n },\n \"aboutSession\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Autopilot's own item: the Session it is about, an id or exact name.\"\n },\n \"returnAfterMs\": {\n \"type\": \"number\",\n \"description\": \"Return waiting after this long; set for MCP clients whose tool calls time out.\"\n },\n \"caller\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"notifyResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"answer\"\n ],\n \"properties\": {\n \"answer\": {\n \"type\": \"string\",\n \"description\": \"options: the chosen label (with questions, the picks joined with \\\" · \\\"). ask: the typed answer.\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n },\n \"answers\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"options with questions: one pick per question, in order.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The person typed a reply: it may answer the question or ask one. The approval keeps waiting; say which with `runpane notify --took <replyId> answer|question` (see next).\",\n \"required\": [\n \"reply\",\n \"id\",\n \"replyId\",\n \"next\"\n ],\n \"properties\": {\n \"reply\": {\n \"type\": \"string\",\n \"description\": \"What the person typed.\"\n },\n \"picks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"What they picked with it, one label per question.\"\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval, for --took and --update.\"\n },\n \"replyId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"This call's private id for the reply (not the id other screens show). Pass it to --took and --update: only this call was given it, so it lets its agent continue, also from outside a Pane tab.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"--took: how the reply was recorded.\",\n \"required\": [\n \"took\",\n \"id\"\n ],\n \"properties\": {\n \"took\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"delivered\"\n ],\n \"properties\": {\n \"delivered\": {\n \"enum\": [\n \"screen\",\n \"inbox\"\n ],\n \"description\": \"screen: shown on the strip. inbox: listed in the Notify inbox because a question was on screen.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person saved the value as project/name. The value is never returned.\",\n \"required\": [\n \"status\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"saved\"\n },\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person declined; nothing was saved.\",\n \"required\": [\n \"status\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"declined\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"waiting\",\n \"id\",\n \"next\"\n ],\n \"properties\": {\n \"waiting\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"unavailable\",\n \"next\"\n ],\n \"properties\": {\n \"unavailable\": {\n \"type\": \"string\",\n \"description\": \"Why nobody can answer through Pane.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"workspaceMachine\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"dnsName\",\n \"os\",\n \"online\",\n \"ips\",\n \"self\",\n \"owner\",\n \"mine\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Tailscale machine name; pass it as `machine`.\"\n },\n \"dnsName\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"ips\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"self\": {\n \"type\": \"boolean\",\n \"description\": \"This machine.\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own login.\"\n }\n }\n },\n \"workspaceListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"self\",\n \"machines\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"self\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"properties\": {\n \"workspace\": {\n \"description\": \"This machine's workspace state; null when Pane is not running.\",\n \"anyOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"enabled\",\n \"state\"\n ],\n \"properties\": {\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n },\n \"machineName\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n }\n },\n \"machines\": {\n \"type\": \"array\",\n \"items\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"required\": [\n \"joined\"\n ],\n \"properties\": {\n \"joined\": {\n \"type\": \"boolean\",\n \"description\": \"Its Pane answers workspace read, write, and exec.\"\n },\n \"info\": {\n \"type\": \"object\",\n \"required\": [\n \"hostname\",\n \"os\",\n \"isWsl\",\n \"shell\",\n \"homeDir\",\n \"wslDistros\"\n ],\n \"properties\": {\n \"hostname\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"isWsl\": {\n \"type\": \"boolean\"\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"homeDir\": {\n \"type\": \"string\"\n },\n \"wslDistros\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why it does not answer.\"\n }\n }\n }\n }\n }\n },\n \"workspaceReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"encoding\",\n \"content\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine read from; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"encoding\": {\n \"enum\": [\n \"utf8\",\n \"base64\"\n ]\n },\n \"content\": {\n \"type\": \"string\",\n \"description\": \"File text, or base64 bytes; a directory lists its entries.\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceWriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine written to; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceExecResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"os\",\n \"shell\",\n \"cwd\",\n \"exitCode\",\n \"signal\",\n \"timedOut\",\n \"stdout\",\n \"stderr\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"The command exited 0.\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"signal\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"stillRunning\": {\n \"const\": true,\n \"description\": \"It timed out and could not be stopped.\"\n },\n \"stdout\": {\n \"type\": \"string\"\n },\n \"stderr\": {\n \"type\": \"string\"\n }\n }\n },\n \"autopilotStackEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"ref\",\n \"title\",\n \"tabs\",\n \"addedAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"ref\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"description\": \"The first tab is the entry's own (its approval, document, URL, or Session).\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"addedAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"About something Autopilot wrote or asked itself.\"\n },\n \"doneAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms when it was resolved (answered, expired, or dismissed).\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"One entry on Autopilot's stack. References only: the item, file, URL, or Session it is about.\"\n },\n \"autopilotStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"enabled\",\n \"chatTabsEnabled\",\n \"entitlement\",\n \"run\",\n \"panelId\",\n \"ownerPaneId\",\n \"state\",\n \"sentinel\",\n \"stack\",\n \"walk\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"enabled\": {\n \"type\": \"boolean\",\n \"description\": \"The experimental switch (Settings › Advanced › Experimental › Autopilot).\"\n },\n \"chatTabsEnabled\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot runs as a chat tab, so it needs chat tabs on.\"\n },\n \"entitlement\": {\n \"type\": \"object\",\n \"required\": [\n \"entitled\"\n ],\n \"properties\": {\n \"entitled\": {\n \"type\": \"boolean\"\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"run\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"folder\",\n \"docsFolder\",\n \"createdAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"folder\": {\n \"type\": \"string\"\n },\n \"docsFolder\": {\n \"type\": \"string\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"panelId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"Autopilot's chat tab, once the run has one.\"\n },\n \"ownerPaneId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"state\": {\n \"enum\": [\n \"stopped\",\n \"running\",\n \"paused\"\n ]\n },\n \"sentinel\": {\n \"type\": \"boolean\",\n \"description\": \"Sentinel Mode: Autopilot keeps Sessions moving while the person is away (turning it on also starts Autopilot).\"\n },\n \"sentinelSince\": {\n \"type\": \"number\",\n \"description\": \"Epoch milliseconds when Sentinel was turned on; absent while it is off.\"\n },\n \"stack\": {\n \"type\": \"object\",\n \"required\": [\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"walk\": {\n \"type\": \"object\",\n \"required\": [\n \"sessions\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"The Session the walk is on.\"\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"waiting\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"waiting\": {\n \"type\": \"number\",\n \"description\": \"Items waiting on the person in this Session.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotWaitingResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Grouped by Session, top first, in the order Autopilot walks them.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"kind\",\n \"agent\",\n \"pane\",\n \"links\",\n \"createdAt\",\n \"ageMs\",\n \"machine\",\n \"machineId\",\n \"onStack\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"approval\",\n \"question\",\n \"permission\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Absent: not in a Session.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n },\n \"pane\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"orchestrator\": {\n \"type\": \"boolean\",\n \"description\": \"The asking tab is the Session's orchestrator.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot asked it itself.\"\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"createdAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"ageMs\": {\n \"type\": \"number\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"machineId\": {\n \"type\": \"string\"\n },\n \"onStack\": {\n \"type\": \"boolean\",\n \"description\": \"Already on the stack.\"\n },\n \"quiet\": {\n \"type\": \"boolean\",\n \"description\": \"A worker's question its orchestrator still has first call on: it shows, but doesn't ring yet.\"\n },\n \"stale\": {\n \"type\": \"boolean\",\n \"description\": \"Unanswered for 3 days: it never rings or pushes again.\"\n },\n \"connected\": {\n \"type\": \"object\",\n \"description\": \"What the item is connected to: its Session, or outside one its Pane, Autopilot's chat or its machine. Absent from an older machine.\",\n \"required\": [\n \"kind\",\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"session\",\n \"pane\",\n \"autopilot\",\n \"machine\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n },\n \"offline\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Saved machines it can't reach. Absent from an older Pane.\"\n },\n \"cantSee\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The person's Tailscale machines not saved here, so their items can't show. Absent from an older Pane.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackPushRequest\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"item\": {\n \"type\": \"string\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Absolute: runpane resolves a relative path against the caller's directory.\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Session id or exact name.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"next\": {\n \"type\": \"boolean\",\n \"description\": \"Right after the entry on screen, so it comes up next.\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"Exactly one of item, file, url, or session.\"\n },\n \"autopilotStackPushResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entry\",\n \"shown\",\n \"position\",\n \"total\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entry\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"shown\": {\n \"type\": \"boolean\",\n \"description\": \"The entry is on screen now (the stack had nothing open).\"\n },\n \"position\": {\n \"type\": \"number\",\n \"description\": \"1-based place on the stack.\"\n },\n \"total\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot answer` sends on the runpane:autopilot:answer daemon channel.\",\n \"required\": [\n \"id\",\n \"answer\",\n \"because\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"answer\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"choice\"\n ],\n \"properties\": {\n \"choice\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"text\"\n ],\n \"properties\": {\n \"text\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"because\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A D-n, a document or the person's words.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot close` sends on the runpane:autopilot:close daemon channel.\",\n \"required\": [\n \"id\",\n \"reason\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot rewrite` sends on the runpane:autopilot:rewrite daemon channel. Link paths are absolute: runpane resolves a relative path against the caller's directory.\",\n \"required\": [\n \"id\",\n \"ui\",\n \"title\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"description\": \"The new item's id.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"pausedMark\": {\n \"type\": \"object\",\n \"required\": [\n \"at\",\n \"by\"\n ],\n \"properties\": {\n \"at\": {\n \"type\": \"string\",\n \"description\": \"When the tab was paused (ISO 8601).\"\n },\n \"by\": {\n \"enum\": [\n \"manual\",\n \"auto\"\n ],\n \"description\": \"manual: a person or agent paused it. auto: auto-pause paused it after idleHours without use.\"\n },\n \"idleHours\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"pauseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"action\",\n \"scope\",\n \"panelIds\",\n \"unchanged\",\n \"failed\",\n \"message\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when a tab could not be resumed; the others still were.\"\n },\n \"action\": {\n \"enum\": [\n \"paused\",\n \"resumed\"\n ]\n },\n \"scope\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"panel\",\n \"pane\",\n \"session\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Tabs this call paused or resumed.\"\n },\n \"chatTabsNotPaused\": {\n \"type\": \"number\",\n \"description\": \"On a pause: chat tabs in scope, which can't pause yet, so their agents keep running.\"\n },\n \"unchanged\": {\n \"type\": \"number\",\n \"description\": \"Terminal tabs in scope that were already paused (for pause) or not paused (for resume).\"\n },\n \"failed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"error\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, then route by the rule 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: if the work's branch already has a Pane (find it with `runpane panes list --repo <repo> --json` by matching the PR's head branch against `git -C <worktreePath> branch --show-current`), open a new agent tab in it with `runpane panels create --pane <id> --agent <agent> --initial-input \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`; only when the branch has no Pane yet, create one with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` (the new Pane is pinned by default; add `--no-pinned` for a throwaway Pane, add `--base-branch <ref>` to branch from something other than the repository default, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`. For long-lived supervision, use `runpane watch --follow` instead of polling wait or screen.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use `panes create` only when the work's branch has no Pane yet (new work, or an existing PR with no Pane), since each new Pane creates a worktree. Every later agent for that branch opens as a new tab in its Pane with `panels create --pane <id>`, never a new Pane. Tabs share one checkout: only the implementer changes the branch or HEAD, one writer at a time; review, audit and QA tabs never run `git checkout`, `reset` or `stash`.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: Pane's skills live in `<PANE_DIR>/skills/pane-chat/skills/`, and the Pane Chat entry point is `<PANE_DIR>/skills/pane-chat/pane-orchestrator/SKILL.md`; read the skill that matches the request, such as `discussion`, `create-ticket`, `tdd`, `prepare-pr`, or `review`.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; `panes create` pins the new Pane into the UI's favorite/pin set by default, so no follow-up `panes pin` call is needed and `--no-pinned` is the opt-out for throwaway Panes.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"Read and change a repo's Project Settings (build, run, and archive scripts, worktree folder, environment variables) with `runpane repos get` and `runpane repos update`; never edit Pane's database by hand.\",\n \"When a command needs a secret (an API key, a token), run it with `runpane secrets exec --project <project> -- <command>`. If the secret is missing, ask the person with `runpane secrets request`; never ask them to paste a secret into the chat, and never print one.\",\n \"If WSL cannot reach Pane, the user may be running Windows Pane; try `/mnt/c/Users/<you>/.pane/bin/runpane doctor --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude|cursor> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes, wait for each agent's input box, and verify its starting message in one call.\",\n \"Use `runpane panels screen` for compact current state, including Claude and Codex composer state. Use `panels wait` for create-time readiness or text checks. Use `runpane watch --follow` to block on workspace transitions (READY, BLOCKED, IDLE, STUCK, EXIT) without polling, or `runpane watch --until-event` to exit on the first one. Use `panels submit` to send and submit a new turn. Use `panels submit-composer --strategy auto` only for a composer that was filled separately.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"Pane terminals draw inline images: sixel, iTerm2 inline images, and the kitty graphics protocol. Tools that need kitty graphics, such as terminal-browser and terminal-doom, run inside a Pane panel; `runpane doctor --json` reports the exact list under `terminal.graphicsProtocols`.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen`. For a harness that consumes streaming output, use `runpane watch --follow` (with `--session <id>` for a Session); its named consumer applies settling and batching. Adding numeric --since leaves it unnamed and bypasses cadence; --as cannot be combined with --since. A harness that wakes only when a process exits uses `runpane watch --as <name> --until-event --json` and runs it again after each event. Single-event waits deliver immediately without follow cadence. Never pipe `--follow` into `grep -m1` or `head`.\",\n \"When several agents share one resource, such as a test account, name a lock in their prompts: each worker runs `runpane lock acquire --name <name> --ttl 30m --wait 1800000 --json` before using it and `runpane lock release --name <name>` after.\",\n \"Loops (`runpane loops create`) run a task on a cron schedule, each run in a new Pane or, with `--session`, as a message to a Session's agent. They are for large recurring jobs, such as a nightly bug sweep, and run at most once an hour. For a short wait, use your agent harness's own sleep and wake instead.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude|cursor>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--build-script <script>\",\n \"--run-script <script>\",\n \"--archive-script <script>\",\n \"--worktree-folder <path>\",\n \"--env <json>\",\n \"--env-remove <name,...>\",\n \"--name <name>\",\n \"--clear <setting,...>\",\n \"--from-json <path|->\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List Pane secrets: project, name, time and machine. Never a value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment.\",\n \"arguments\": [\n \"--project <project>\",\n \"--only <name,...>\",\n \"--env <VAR=NAME>\",\n \"--file <name>\",\n \"-- <command>...\"\n ]\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card; returns saved or declined, never the value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--name <name>\",\n \"--title <text>\",\n \"--reason <text>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--pinned\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--session <id|name>\",\n \"--merged\",\n \"--closed\",\n \"--source <user|agent>\",\n \"--force\",\n \"--remove-worktree\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--name <new-name>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally a panel) on explicit user request.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--panel <panel-id>\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open reviewer, QA, fix or helper agent tabs inside an existing Pane; they share that Pane's worktree. The default for any later agent on existing work.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Show the user an HTML page, plan, report, dev server URL, or file as a tab in split view beside the agent.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--url <url>\",\n \"--file <path>\",\n \"--title <title>\",\n \"--split\",\n \"--tab\",\n \"--no-focus\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened once it no longer matters; refuses the person's tabs, busy agents and unsent input.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--dry-run\",\n \"--force\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the key for its current state.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter|tab>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place (same panel id), resuming its agent's conversation.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--session <id|name>\",\n \"--all-stopped\",\n \"--concurrency <count>\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace events from the durable daemon journal; follow uses conservative cadence and quiet expiry by default.\",\n \"arguments\": [\n \"--follow\",\n \"--until-event\",\n \"--session <id|name>\",\n \"--self-test\",\n \"--kinds <kind,...>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question wherever they are and wait for the answer; prefer it to asking in the terminal.\",\n \"arguments\": [\n \"--ui <options|approve|ask|info>\",\n \"--title <text>\",\n \"--choices <json>\",\n \"--links <json>\",\n \"--resume <id>\",\n \"--json\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update Pane and the active supported CLI with durable status.\",\n \"details\": \"Defaults to both components. Use --pane-only or --cli-only to select one. Preflight checks installation ownership and supervisor capability before downtime.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Requested app and CLI release version.\"\n },\n {\n \"name\": \"--pane-only\",\n \"required\": false,\n \"description\": \"Update only the installed Pane app.\"\n },\n {\n \"name\": \"--cli-only\",\n \"required\": false,\n \"description\": \"Update only the invoked supported CLI installation.\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<operation-id>\",\n \"required\": false,\n \"description\": \"Read durable status without a running Pane.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Wait until the operation completes or requires attention.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Inspect preflight without staging or stopping Pane.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Requires standalone Node and a current-user OS supervisor. npm, managed CLI, and supported pip environments retain their actual ownership. System-package Linux and installations requiring elevation are refused before downtime.\",\n \"The operation preserves a pre-migration data snapshot; it never blindly rolls an old app or database over migrated state.\",\n \"The initial result can be accepted or staging; it is not a claim that the update or restoration completed.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Accepted and ignored, so one --pane-dir can be passed to every runpane command.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n },\n {\n \"name\": \"--report\",\n \"required\": false,\n \"description\": \"Prepare a redacted, inspectable diagnostic report.\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Report title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm creating exactly one greenfield-inc/Pane issue.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\",\n \"runpane doctor --report --title \\\"runpane watch failed\\\" --body-file ./watch.txt --json\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"daemon repair\": {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"details\": \"Use this only after doctor reports a stale or broken remote-daemon launcher. The command invokes the installed Pane executable in service-repair mode and does not rerun remote setup.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Remote daemon data directory; defaults to ~/.pane_remote.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Existing Pane executable used to perform repair.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip the service restart confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print one machine-readable repair result.\"\n }\n ],\n \"examples\": [\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"notes\": [\n \"Repair does not create pairing credentials, change tunnel configuration, or download Pane.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\",\n \"agentContextUnknownCommandError\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\",\n \"An unknown `--command` exits 2. With `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout, where `candidates` lists the closest command names first; without `--json` the same candidates go to stderr.\",\n \"`--pane-dir <path>` is accepted and ignored, so one `--pane-dir` can be passed to every runpane command.\"\n ]\n },\n \"mcp\": {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"details\": \"Use this to register Pane with an MCP client. Pane registers it with Claude Code, Codex, and Cursor automatically; each tool mirrors one runpane command and returns its --json result.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated toolsets to serve: core (default), all, read, repos, panes, panels, agents, sessions, loops, workspace, git, docs, links, admin.\"\n },\n {\n \"name\": \"--read-only\",\n \"required\": false,\n \"description\": \"Serve only read-only tools.\"\n }\n ],\n \"examples\": [\n \"claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \"codex mcp add pane -- npx --yes runpane@latest mcp\"\n ],\n \"notes\": [\n \"Only the npm package and the Pane app include the MCP server; the Python package prints how to run it with Node.\",\n \"Commands that need --yes in the CLI take `yes: true` as a tool input.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry with `/mnt/c/Users/<you>/.pane/bin/runpane repos list --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"repos get\": {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"details\": \"Use this before changing a repo's settings, or to find out what a new Pane will run. Each script reports `value` (saved in Project Settings), `fallback` (from the repo's config file), and `effective` (what Pane runs: the saved value when set, otherwise the fallback). `envVars` lists the project environment variables: plain values in full, keychain items and Pane secrets as references, never their values.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name. Defaults to active.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable settings.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos get --repo Pane --json\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"notes\": [\n \"Config-file fallbacks are read from the repository root. At run time Pane reads them from each Pane's worktree, so a branch that changes its config file can run something else.\",\n \"`currentBranch` is detected from the repository; it is not a setting.\"\n ]\n },\n \"repos update\": {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"details\": \"Saves through the same path as the Project Settings dialog: Pane drops its cached project context, rebuilds the run commands, and open windows reload the repo. Settings you do not pass keep their value. Clear a setting with `--clear` or an empty value; a cleared script falls back to the repo's config file. --env adds or replaces project environment variables by key, and --env-remove removes them. A variable is plain text, an existing macOS keychain item, or a Pane secret reference; to store a new secret value, use `runpane secrets request` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Repository display name. It cannot be cleared.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Project variables to add or replace by key: a JSON array of {key, source: \\\"plain\\\", value}, {key, source: \\\"keychain\\\", service, account} or {key, source: \\\"secret\\\", project, name}.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"required\": false,\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"JSON settings object with keys name, buildScript, runScript, archiveScript, worktreeFolder, openIdeCommand, systemPrompt, and envVars ({set: [...], remove: [...]}); null clears. Flags override it.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Return the settings as they would be after the update without saving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --yes --json\",\n \"runpane repos update --repo Pane --clear archive-script --dry-run --json\",\n \"runpane repos update --repo Pane --from-json settings.json --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"notes\": [\n \"`changed` lists the settings whose saved value differs from before.\",\n \"The worktree folder applies to Panes created after the update; existing worktrees stay where they are.\",\n \"Plain values are saved and shown in full; do not put a secret in a plain variable. Point the variable at a Pane secret instead.\"\n ]\n },\n \"repos rename\": {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"details\": \"Use this to replace an automatic repository name, such as one with a numeric suffix, with a clear one. It changes only the display name.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository to rename: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"New non-empty repository name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated repository.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos rename --repo 3 --name Lev --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"notes\": [\n \"The repository path, Panes, and settings are unchanged.\"\n ]\n },\n \"secrets list\": {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"details\": \"Use this to find the project and name to pass to `runpane secrets exec`. Each entry has project, name, updatedAt and machine. storage says how this machine keeps values; encrypted false means a plain 0600 file on a machine without an OS keyring.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": false,\n \"description\": \"Only this project's secrets.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets list --json\",\n \"runpane secrets list --project stripe --json\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"notes\": [\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets set\": {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"details\": \"The value comes only from stdin, so it never appears in argv or a transcript. One trailing newline (\\\\n or \\\\r\\\\n) is dropped; an empty value is refused. If stdin is a terminal it is refused too: pipe the value in, or have the person use Settings > Secrets or `runpane secrets request`. With --json it prints {ok, replaced, secret} (secretSetResult).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretSetRequest\",\n \"secretSetResult\"\n ],\n \"notes\": [\n \"Never set a value someone pasted into the chat; ask with `runpane secrets request` instead, so the value stays out of the transcript.\",\n \"There is no MCP tool for this command: MCP arguments land in the transcript.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets delete\": {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"details\": \"Deletes <project>/<name> here. Each other machine deletes it the next time a desktop or the phone syncs with it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"notes\": [\n \"A secret that does not exist fails with SECRET_NOT_FOUND.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets exec\": {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"details\": \"Reads the secrets from this machine's Pane when it runs, starts the command with your environment plus the secrets, passes stdin, stdout and stderr through, and exits with the command's exit code. By default it injects every secret in the project under its own name. --only limits that to the names given. --env VAR=NAME injects secret NAME as VAR. --file NAME writes secret NAME to a 0600 file in a private temp folder and sets NAME to the file's path; the file is removed when the command exits. Injected names override your environment.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Inject only these secrets, comma-separated.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME>\",\n \"required\": false,\n \"description\": \"Inject secret NAME as VAR. Repeatable.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Write secret NAME to a private temp file and set NAME to its path. Repeatable.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command to run and its arguments.\"\n }\n ],\n \"examples\": [\n \"runpane secrets exec --project stripe -- pnpm test:webhooks\",\n \"runpane secrets exec --project stripe --env STRIPE_API_KEY=STRIPE_SECRET_KEY -- node scripts/sync.js\",\n \"runpane secrets exec --project gcp --file SERVICE_ACCOUNT_JSON -- sh -c 'gcloud auth activate-service-account --key-file \\\"$SERVICE_ACCOUNT_JSON\\\"'\"\n ],\n \"notes\": [\n \"Target names PATH, WORKTREE_PATH and PANE_*, in any case, are refused with SECRET_RESERVED_NAME, and nothing runs.\",\n \"A missing secret fails with SECRET_NOT_FOUND before anything runs; ask for it with `runpane secrets request`.\",\n \"Do not echo a secret from inside the command: its output lands wherever the command's output goes, such as your transcript.\",\n \"There is no MCP tool for this command; run it in a shell.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\",\n \"Secrets are the machine owner's: every secrets command in a teammate's `runpane workspace exec` (PANE_TEAMMATE_LOGIN set) fails with SECRET_OWNER_REQUIRED.\"\n ]\n },\n \"secrets request\": {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"details\": \"Shows a Notify card with your title and reason, a masked field, Save securely and Decline. What the person types is saved on this machine as the Pane secret <project>/<name> and synced to their other machines. You get back {status: \\\"saved\\\", project, name} or {status: \\\"declined\\\"}, never the value. Then use it with `runpane secrets exec`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project, such as the service or repo it belongs to: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name, such as STRIPE_SECRET_KEY: letters, digits and underscores, not starting with a digit.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What the secret is, in a few words, such as \\\"Stripe test secret key\\\".\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What you need it for, in one sentence.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook integration tests against Stripe's test mode.\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"A result of {waiting: true, id} means the card is still open and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now. Do not ask for the secret in the chat; follow next.\",\n \"Saving replaces a secret that already exists under that name; the card says so.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\",\n \"`status` is `running` while a tool or agent terminal is live, otherwise the stored lifecycle value (`error` and `initializing` always show as stored). The Pane's dock shell (`dock: true` in `panels list`) does not count, so a Pane whose only live terminal is the dock reads its stored value, usually `stopped`. `agentState` is the most urgent state of the live agent panels: `blocked`, `working`, `ready`, or `none`. `agentStatus` (`active`/`idle`) is kept for older clients.\"\n ]\n },\n \"panes cost\": {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"details\": \"Use this to attribute estimated workspace token costs to Pane lifetimes and inspect the models and cache efficiency behind each Pane's spend.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Return only the selected Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes cost --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"notes\": [\n \"The report range is the last 30 days.\",\n \"Costs are estimates based on API token prices.\",\n \"The unattributed bucket reconciles the Pane rows against totals in the unscoped result.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use this only when the work's branch has no Pane yet: new work, or an existing PR with no Pane. Each new Pane creates a worktree that checks out, installs and often builds, so creating several at once can freeze the machine. First find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`. For review, follow-up review, fixes, QA or any other agent on work that already has a Pane, use `runpane panels create --pane <id>` to open a new agent tab in that Pane instead. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch the new worktree from this ref (for example origin/main or release/2.4) instead of the repository default.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree and branch name; defaults to --name. Characters outside [a-z0-9-] are removed.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"required\": false,\n \"description\": \"Ref to branch from, such as origin/main or release/foo. --base is an alias.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Exact new branch name, slashes included (for example agents/w5a). Fails if the branch exists; never renamed to be unique. Defaults to the worktree name.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree directory name; defaults to --name.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--pinned\",\n \"required\": false,\n \"description\": \"Accepted for backward compatibility and now a no-op; `panes create` already pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Create the pane unpinned instead of the default pinned (the Pane UI's favorite/pin star). Pinning never implies focus.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name w5a --base origin/main --branch agents/w5a --agent <agent> --prompt-file prompt.md --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent claude --initial-input-file brief.md --as-file-pointer --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: before creating one, find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`, and add a tab with `panels create --pane <id>` when it exists.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"`panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`: Pane creates the worktree on exactly that branch name. `--branch` is checked with `git check-ref-format --branch` and fails if the branch already exists; `--worktree-name` still names the directory.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved. `panes create` pins the new Pane by default, so a follow-up `panes pin` call is unnecessary; pass `--no-pinned` for throwaway shells or bulk imports. `--pinned` is still accepted and is now a no-op. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input. A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"With --wait-ready, `initialInput.delivery` reports `taken`, `queued`, `in-composer` or `unknown` with its evidence (`transcript`, `screen`, or `argv` for a prompt passed at launch); verifiedSubmitted is true for `taken` or `queued`. Routing initial input alone does not imply verified submission, and the create's `ok` is false unless a Claude or Codex starting message is verified.\",\n \"If initialInput.blocked.kind is submission_unverified, do not submit again automatically. Inspect initialInput.staged and attempts, then run nextCommand to resolve the ambiguous composer state.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, use Pane's own `runpane` (see doctor) and select the saved WSL repo by name or id, for example `runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json`.\",\n \"Use --wait-ready when an agent needs the started agent at its input box, and its starting message verified, instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"At most 2 Panes run setup at once (worktree file sync, dependency install, build script); more wait in line. A queued `panes create` returns the Pane id right away with `setupQueue` ({ position, message }) and no `panelId`; the agent starts when setup finishes. Find its panel later with `runpane panels list --pane <id> --json`.\",\n \"A Pane with its own worktree starts at the remote's current tip of a remote-tracking base (the default `origin/main`, or one named with `--base`): Pane fetches that branch first, waiting at most about 10 seconds, and creates started together share the fetch. Each item's `base` reports `{ ref, commit, refresh }`, where `refresh` is `fetched`, `recent` (reused a fetch from the last 30 seconds), `skipped` (a local branch or commit base) or `failed`. A failed refresh never fails the create; the Pane starts from the local ref and `base.notice` says what happened and how to catch up.\",\n \"`panes create --branch <name>` fails with code `ERR_RUNPANE_BRANCH_HAS_PANE` when another Pane already has that branch checked out. The message names the Pane and gives the next step: `runpane panels create --pane <id> --tool-command \\\"<cmd>\\\" --source agent --no-focus`.\"\n ]\n },\n \"panes adopt\": {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane.\",\n \"details\": \"Validates that the canonical path is a registered worktree of the selected saved repository, then creates a Pane record without creating, syncing, building, removing, or recreating the directory.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Saved repository selector.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<dir>\",\n \"required\": true,\n \"description\": \"Existing git worktree path.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Agent whose terminal to create; with --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom command to stage or launch instead of a built-in agent, such as a wrapper that runs one.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Persist this agent session id and stage its resume command.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Top-level sidebar folder.\"\n },\n {\n \"name\": \"--launch\",\n \"required\": false,\n \"description\": \"Run the agent (or its resume command) immediately.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sent once the launched agent is ready. Requires --launch.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Alias for --initial-input-file; read the prompt from a file or stdin. Requires --launch.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for the launched agent to be ready and the prompt submitted before returning. Requires --launch.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Do not pin the adopted pane.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the adopted pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview only.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent codex --resume <id> --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --tool-command \\\"agent-farm run free-range\\\" --agent claude --launch --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent claude --launch --prompt-file prompt.md --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"The resume command is staged without Enter unless --launch is passed.\",\n \"Archiving never deletes an adopted worktree unless `runpane panes archive --remove-worktree` is used.\",\n \"One of --agent or --tool-command is required.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"--resume needs a built-in agent command; a wrapper command resumes its own way.\",\n \"The adopted Pane reports `status: running` in `panes list` while its tool terminal is live.\",\n \"A prompt (--prompt, --initial-input, --prompt-file, --initial-input-file) or --wait-ready needs --launch; without it adopt fails instead of dropping the prompt.\",\n \"With --launch the result carries the same readiness, initialInput, and nextCommand fields as panes create. With --resume the prompt goes to the resumed conversation's composer once the agent is ready.\",\n \"To start new work on a named branch, prefer `panes create --base <ref> --branch <name>` over `git worktree add` plus adopt.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. The safety check refreshes the configured upstream before comparing it to HEAD and includes exact unpushed commit IDs and subjects. When the branch has no upstream or its upstream is gone (GitHub deletes merged branches), a merged pull request whose head is HEAD counts as pushed, so squash-merged branches archive without --force. Refuses unsafe archive operations unless --force is used. Use --dry-run to inspect the same evidence without mutating Pane state. Use --session <id|name> --merged to close out every finished Pane of a Session at once. Archive also refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless --force is used, since archiving stops the agent or loses the input. Add --closed to the Session sweep to also archive Panes whose PR closed without merging.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane/session id to archive. Required unless --session is used.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn or waiting for an answer (archiving stops it), or a tab holds unsent input (archiving loses it).\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"required\": false,\n \"description\": \"Also check and remove an adopted Pane's worktree. Pane-managed worktrees are always removed; the local branch is always kept.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Archive the Panes associated with this named Session instead of one Pane. Requires --merged.\"\n },\n {\n \"name\": \"--merged\",\n \"required\": false,\n \"description\": \"With --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work; skip the rest with a reason and next step. An open PR is skipped as pr-open, no PR or a closed unmerged PR as pr-not-merged, and a PR state GitHub cannot confirm as pr-status-unknown.\"\n },\n {\n \"name\": \"--closed\",\n \"required\": false,\n \"description\": \"With --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks. Without --closed they are skipped as pr-not-merged.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Refresh and print archive safety evidence without archiving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --dry-run --json\",\n \"runpane panes archive --pane <pane-id> --remove-worktree --yes --json\",\n \"runpane panes archive --session <id|name> --merged --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --closed --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --remove-worktree --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"Inspect safetyCheck.unpushedCommitDetails for exact commit IDs and subjects; upstreamRefreshed confirms the tracking ref was refreshed first.\",\n \"A --dry-run result sets dryRun:true and wouldArchive without deleting the Pane or its worktree.\",\n \"A successful archive reports worktreeCleanup `completed` once the worktree is gone from its path and from git. trashDeletion `pending` means a large worktree (for example one with node_modules) is still being deleted from .git/pane-trash in the background; that is still ok:true. `timeout` (also ok:true) means removal is still running; only `failed` sets ok:false.\",\n \"safetyCheck.mergedViaPr names the merged PR whose head is HEAD; it needs an authenticated `gh`. Without it, a squash-merged branch whose remote branch was deleted reports unpushed-commits.\",\n \"An adopted Pane (panes adopt) keeps its worktree unless --remove-worktree is passed. `panes restore` does not recreate a removed adopted worktree; the local branch is kept, so `git worktree add <path> <branch>` brings it back.\",\n \"With --session --merged, each item has outcome archived, would-archive (dry run), skipped (with skipped.code and skipped.message), or failed.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\",\n \"Undo an archive with `runpane panes restore --pane <pane-id> --yes`, which recreates the worktree. `runpane links create --pane <pane-id>` gives the user a link to review the Pane first.\",\n \"When the safety check is skipped, `safetyCheck.reason` says why: `external-worktree` (an adopted Pane whose worktree Pane does not own), `main-repo`, `missing-project-context`, or `git-error`. `worktreeWillRemain: true` means archiving leaves the worktree on disk.\",\n \"An agent mid-turn or waiting for an answer blocks archive with `agent-busy` (`safetyCheck.busyAgents` lists them); wait until it is idle with `runpane panels wait --panel <id> --for idle`, then archive again. The Session sweep skips such a Pane with skipped.code `agent-busy`.\",\n \"A tab holding text typed into an agent's composer but not sent, or an editor with unsaved edits, blocks archive with `unsent-input` (`safetyCheck.unsentInput` lists them), the same input `panels close` refuses to lose; send, save or clear it, then archive again. `--force` discards it.\"\n ]\n },\n \"panes pin\": {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to pin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pin --pane <pane-id> --yes --json\",\n \"runpane panes pin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.\",\n \"Pinning does not focus the Pane.\"\n ]\n },\n \"panes unpin\": {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to unpin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes unpin --pane <pane-id> --yes --json\",\n \"runpane panes unpin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.\",\n \"Unpinning does not focus the Pane.\"\n ]\n },\n \"panes pause\": {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"details\": \"Pauses every terminal tab of a Pane, run scripts included, until `panes resume` resumes it. A chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`. A Pane reads as paused (`panes list` `paused`) while all of its terminal tabs are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pause --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Find idle Panes with `panes list --json`: `inactiveSince` is the latest terminal output or input in its tabs, or the last time the person selected it.\",\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panes resume\": {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"details\": \"Resumes a Pane's paused tabs. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes resume --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"panes rename\": {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"details\": \"Trims and sets the Pane display name through the same persistence and session-updated event path used by the UI. Dry-run returns the projected updated pane without mutating.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to rename.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<new-name>\",\n \"required\": true,\n \"description\": \"New non-empty Pane/session name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and return the projected updated pane without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated pane.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"notes\": [\n \"Empty and whitespace-only names are rejected; no additional maximum length is imposed beyond the existing UI and database behavior.\",\n \"The JSON result includes the updated pane summary so callers do not need a second panes list request.\"\n ]\n },\n \"panes focus\": {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels).\",\n \"details\": \"Brings the Pane desktop window to the foreground and selects the requested Pane exactly like clicking it in the sidebar; when --panel is given it also selects that panel/tab inside the Pane. This steals window focus, so it must only be run on an explicit user request to open, focus, or show a Pane, never proactively. Focusing does not change the Pane's worktree, branch, panels, or pinned state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to focus. Must be an active (non-archived) Pane.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Optional panel/tab id inside the Pane to select; must belong to the focused Pane.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change focus behavior.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands; required in non-interactive contexts because focusing steals window focus.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes focus --pane <pane-id> --source user --yes --json\",\n \"runpane panes focus --pane <pane-id> --panel <panel-id> --source user --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"notes\": [\n \"Run this only when the user explicitly asks to open, focus, show, or switch to a Pane; never focus a Pane proactively because it steals the user's window focus.\",\n \"Archived Panes cannot be focused; the command fails clearly instead of falling back to focusing another Pane.\",\n \"If --panel does not belong to the focused Pane, the command fails clearly rather than selecting a different panel.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\",\n \"`agentType` is the agent Pane treats the panel as; `agentDetection` says how it knows (`declared`, `command`, `process`, or `screen`), and `launchCommand` is the command the panel was launched with.\",\n \"`resumeId` is the agent session ID Pane saved for reopening the panel, and `resumeSource` says which rule resumes with it: `agent-farm` (an `agent-farm run` or `agent-farm agent` command), `configured` (the panel's own resume command), or `agent` (a built-in agent's captured session). Both are omitted when no ID is saved.\",\n \"Every Pane has one plain shell titled `Terminal`, its terminal dock: `dock: true` marks it, and the field is omitted on every other panel. Setup progress notices appear there; it is not a tool or agent tab and does not count toward the Pane's `status`. A `--tool-command` tab without `--title` or `--agent` is titled from its command (whole words up to 40 characters).\",\n \"`openedBy` says who opened the tab through runpane: `source` (`agent` or `user`) and, when known, `panelId` of the tab that opened it. It is omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose `openedBy.source` is `agent`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters. A chat tab has no terminal: input fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated key names to press in order: enter, escape, tab, backspace, space, up, down, left, right, ctrl-c, ctrl-d, or any single character.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\",\n \"runpane panels input --panel <panel-id> --keys down,enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"To answer a menu or prompt, prefer `--keys` (for example `--keys down,enter`) over escape sequences in `--text`.\",\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. On a chat tab it returns the newest transcript lines with source acpTranscript. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"The composer object reports whether a Claude or Codex composer is present and whether it holds undelivered text. Placeholder and suggestion text (drawn dim or grey, such as Claude's suggested next prompt) does not count as undelivered text; `composer.ghostText` carries it, and text output marks its line with `⟨suggestion⟩`.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"details\": \"Use this for ordinary interactive submissions. For Claude, Pane waits for the composer to appear and show the staged text, then sends Enter on its own and verifies that the composer cleared; Claude keeps an Enter that arrives together with the text as a newline. For a Codex composer, Pane stages the text, waits for paste handling, then queues with Tab while working or submits with Enter when ready, and verifies delivery when visible evidence is available. Wrapper-launched agents are handled the same way once Pane knows the agent (declared, from the foreground process, or from the screen). A Claude or Codex panel with no composer on screen gets nothing: the result is `ok: false` with `blocked.kind: composer-unknown`. Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working. Other terminals receive a normalized CR Enter. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--interrupt\",\n \"required\": false,\n \"description\": \"Interrupt the current turn and wait up to 30 seconds for an empty idle composer before delivering. Escape for Codex/Claude; Ctrl+C for Cursor.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --input-file prompt.md --as-file-pointer --wait --yes\",\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\",\n \"runpane panels submit --panel <panel-id> --input-file brief.md --as-file-pointer --yes --json\",\n \"runpane panels submit --panel <panel-id> --text \\\"Stop that approach and follow this correction\\\" --interrupt --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes sequenceName, verifiedSubmitted, delivery and nextCommand. For Claude and Codex, `delivery.state` is `taken` (the agent started a turn with the text), `queued` (it holds the text until its current turn ends), `in-composer` (the text is still in the composer) or `unknown`, and `delivery.evidence` is `transcript` (the agent's own transcript) or `screen`. verifiedSubmitted is true for `taken` or `queued`; never resend those. If ok is false, inspect blocked and do not assume the turn started.\",\n \"Do not follow `panels submit` with `panels submit-composer`; Claude and Codex composer submission is handled atomically.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\",\n \"On `blocked.kind: composer-unknown`, nothing was typed. Check `panels screen`; if the agent is at a prompt Pane does not recognise, use `panels input`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"With --interrupt, Pane sends the stop key only while the agent is active, waits for an empty idle composer, then submits. If readiness times out, replacement text is not sent. A queued result is not read yet: delivery.message explains the delay; never resend queued text. Interrupt success requires delivery.state: taken. Unknown agents and occupied composers are rejected without typing.\",\n \"On a chat tab, submit sends an ACP prompt and returns sequenceName=acp, enter=none, operationId and acp-receipt delivery evidence; a prompt sent while the agent works is queued and sent next. Do not blindly resend after a lost response.\"\n ]\n },\n \"panels resume\": {\n \"name\": \"panels resume\",\n \"summary\": \"Restart stopped terminal panels in place, resuming their agents' conversations: one panel, a Session's panels, or every stopped agent panel.\",\n \"details\": \"Use this when `panels list`, `panels screen`, or `panels wait` report `running: false`, or `watch` reports `panel.stopped`, typically after a Pane restart or once its agent exited back to the shell. The panel keeps its id. Pane re-runs the panel's saved launch command resolved to the agent's resume command (Claude `--resume <session>`, `codex resume <session>`, Cursor `--resume`), the same restart the app runs when it shows the panel. It never takes focus. A panel whose agent exited back to its shell has its saved command run again in that shell. On a running panel it does nothing and returns `action: \\\"already-running\\\"`. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope (plain shells and paused tabs are left alone), `--concurrency` at a time, skips running panels, and returns one result per panel; one failure does not stop the others.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Terminal panel id. Give exactly one of --panel, --session, or --all-stopped.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of this Session's associated Panes.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of every non-archived Pane.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10. With --wait-ready the resumed panels then wait together, so the call takes the restarts plus one --ready-timeout-ms.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the resumed agent is ready, with the same startup-screen checks as `panels create --wait-ready`.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"notes\": [\n \"If readiness.blocked is present, the agent is at a startup prompt (trust, update, [y/n]); inspect `panels screen` or follow blocked.suggestedCommand. Ask the user before answering trust or permission prompts.\",\n \"A Claude panel resumes the conversation whose session id Pane launched it with; a Codex panel resumes the session id Pane captured from its output. Without one, the panel relaunches its original command.\",\n \"A bulk resume is ok only when every panel resumed and, with --wait-ready, became ready. Check items[].readiness.blocked and items[].error per panel.\"\n ]\n },\n \"panels close\": {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"details\": \"Closes a tab through the same path as closing it in Pane, so a terminal tab's process stops. It never archives the Pane or touches its worktree. Only a tab an agent opened can close: Pane records the opener when a tab is opened with `--source agent` (panels open, panels create, panes create, agents start). It refuses, without closing, a tab not opened by an agent (the person's, and every tab from before Pane recorded openers), a permanent tab, an agent mid-turn or waiting for an answer, unsent composer text or unsaved editor edits, and a live tab whose input Pane cannot read (agents other than Claude and Codex, and chat tabs). A refusal exits 1 with `refused.code`, `refused.message` and `refused.next`. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports the same without closing. `--force` (only for a stuck tab, or one the person asked you to close) closes it anyway, as the tab's close button does: a busy agent's turn and unsent input are lost, and the result says `forced: true`. A permanent tab still stays.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tab to close.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Close it even if an agent didn't open it, its agent is busy, or it holds unsent input. Only for a stuck tab, or one the person asked you to close.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels close --panel <panel-id> --dry-run --json\",\n \"runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"notes\": [\n \"ok:false with `refused` means the tab was NOT closed. `refused.code` is opened-by-user, permanent, agent-busy, unsent-input, or input-unknown; follow `refused.next`. Use --force only when the tab is stuck or the person asked you to close it.\",\n \"`alreadyClosed: true` (ok:true, closed:false) means no tab has that id any more.\",\n \"With --dry-run, `wouldClose` says whether the close would go ahead and `refused` says why not. Nothing closes; a refused dry run also exits 1.\",\n \"Open tabs with `--source agent` so they count as yours; `panels list` shows who opened each tab as `openedBy`.\"\n ]\n },\n \"panels pause\": {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"details\": \"Pauses one terminal tab until `panels resume --panel <panel-id>`, or its Pane or Session, resumes it. A chat tab can't pause yet: pausing one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE` and changes nothing. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels pause --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command. On a chat tab, a text wait (`--for text`) searches only what the agent writes after the wait starts, plus a reply still streaming then, never the person's prompts.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"If stopped is true, the panel's terminal is not running (for example after a Pane restart); run nextCommand (`panels resume`) instead of waiting again.\",\n \"The default timeout and screen are intentionally small for agent context safety.\",\n \"On a chat tab, status comes from the host ACP session (a turn that ended is ready, a pending approval or question is blocked) and the screen field has source=acpTranscript: transcript text, not a terminal screen.\"\n ]\n },\n \"panels last-message\": {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"details\": \"Use this when a worker is READY and you need what it said, such as its PR number or result. It reads the Claude or Codex transcript Pane found for the panel and never falls back to the screen.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Agent panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum characters to return; defaults to 20000. The end of the reply is kept.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels last-message --panel <panel-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"notes\": [\n \"Without a transcript (a shell panel, another agent, or no reply yet) the result is `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and the command exits 1. Use `panels screen` then.\",\n \"A worker that ran `runpane report` already handed back its state, PR, head, and summary; read those with `agents status` before reading the whole reply.\"\n ]\n },\n \"panels conversation\": {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"details\": \"Pane calls this for you: the Claude Code SessionStart hook that Agent Farm and Pane's plain claude tabs install runs `runpane panels conversation --hook` on start, /resume, /clear and compact. Run it yourself only to tell Pane a panel is on a conversation it could not learn from the hook.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Conversation (agent session) id the panel is on.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<startup|resume|clear|compact>\",\n \"required\": false,\n \"description\": \"What started the conversation.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id; defaults to PANE_PANEL_ID inside a Pane terminal.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id, with --panel; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels conversation --conversation <id> --source resume --json\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"notes\": [\n \"`sure` in the result is true when the report came since the panel's agent started; a Switch to chat or restart opens a sure conversation, and otherwise opens the folder's most recent one.\",\n \"`--hook` reads the hook's stdin, prints nothing and always exits 0; it is not offered over MCP.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent tab (reviewer, auditor, QA, fix or helper) inside an existing Pane, sharing its worktree. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: use this, not panes create, for any later agent on existing work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. This is the default way to start any later agent on work that already has a Pane, including reviewers, follow-up reviewers, auditors, fix implementers, QA and clean-context helpers. The new tab is a fresh agent that shares the Pane's worktree and branch; it does not create a new worktree. Review, audit and QA tabs never run `git checkout`, `reset` or `stash`, and a fix tab starts only after the previous writer has stopped. Use `panes create` only when the work's branch has no Pane yet. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--chat\",\n \"required\": false,\n \"description\": \"Open --agent claude or codex as a chat tab (ACP, no terminal) instead of a terminal tab. Needs chat tabs on in Settings > Advanced.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"required\": false,\n \"description\": \"Start this Agent Farm profile in a chat tab; Agent Farm prints its ACP launch (agent-farm run --print-acp).\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"required\": false,\n \"description\": \"A launch argument for --agent-farm; repeatable.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command \\\"agent-farm run free-range\\\" --agent claude --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"`--chat` or `--agent-farm` creates a chat tab: --initial-input is its first prompt, sent once when the conversation starts; --wait-ready waits for the agent to start. `panels submit`, `wait`, `screen`, `output`, `last-message`, `report` and `resume` work on chat tabs; `panels input` and `submit-composer` do not.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels open\": {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"details\": \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open the tab in; defaults to PANE_SESSION_ID (the calling panel's Pane, including a Session orchestrator's).\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"http(s) or file URL to open in a browser tab. WSL file URLs use the same automatic path conversion as --file. Pass exactly one of --url or --file.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"File inside the Pane worktree, relative or absolute. WSL panes accept Linux, Windows drive, and WSL UNC paths without manual conversion. Files open in an editor tab; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Tab title override.\"\n },\n {\n \"name\": \"--split\",\n \"required\": false,\n \"description\": \"Open in split view beside the primary tab group (default).\"\n },\n {\n \"name\": \"--tab\",\n \"required\": false,\n \"description\": \"Open in the current tab group instead of split view.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Open the tab without selecting it.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Select the tab (default). Never raises or focuses the Pane window.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels open --file plan.html --source agent --yes --json\",\n \"runpane panels open --url http://localhost:3000 --source agent --yes --json\",\n \"runpane panels open --pane <pane-id> --file src/app.ts --tab --source agent --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"notes\": [\n \"Prefer this over opening the system browser when showing the user a page, plan, report, or dev server.\",\n \"Write plans and reports as self-contained HTML files. Use --url with a file URL to render a page in a live browser, or --file to open its editable source; reopening the same target reuses its tab.\",\n \"When the user views Pane from another computer, a file page loads only files in its own folder and the folders inside it. Keep the page's CSS, scripts and images there, put them inline, or serve the folder over HTTP and open its localhost URL. Give file URLs as the host sees them, not as the other computer would.\",\n \"Opening a tab does not steal window focus; use `panes focus` only on explicit user request.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer. Auto uses Tab to queue while Codex is working and Enter when ready; Pane verifies that the text left the composer. A chat tab has no composer to submit: it fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName, verifiedSubmitted and, for Claude and Codex, delivery (`taken`, `queued`, `in-composer` or `unknown`, with `transcript` or `screen` evidence). If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\",\n \"With `--strategy auto`, if the staged text is still visible in the composer after the first attempt, Pane retries once and checks again: Tab for working Codex, Enter otherwise. It never retries an empty composer.\"\n ]\n },\n \"workspace state\": {\n \"name\": \"workspace state\",\n \"summary\": \"Read one snapshot of every Pane and CLI panel.\",\n \"details\": \"Use this instead of spawning one command per pane or panel.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optionally limit the snapshot to one saved repository.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane workspace state --json\"\n ],\n \"notes\": [\n \"The response carries the daemon epoch and current journal generation.\"\n ]\n },\n \"watch\": {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace transitions using the daemon journal.\",\n \"details\": \"`runpane watch --follow` is the monitor for an agent: with no other flags it prints WATCH OK once, then one line per event (reports, ready, blocked, idle, exits, Session membership, and PR conflicts, checks, and merges), batches non-urgent lines, and ends itself with WATCH EXPIRED after 3 hours with nothing happening. Set individual duration flags to override these defaults. `--until-event` is for harnesses that wake an agent when a process exits (Claude Code background Bash, Codex): it prints the first event worth waking for immediately, without follow settling or batching, and exits 0, and its named cursor means nothing is missed between runs. Every default is listed in the contract under defaults.watch; each override flag replaces one value.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--follow\",\n \"required\": false,\n \"description\": \"Keep watching, with the defaults in defaults.watch.follow: WATCH OK once, then one line per event; READY after 3m of quiet, BLOCKED after 30s, other lines batched at most every 10m (REPORT, BLOCKED, PR conflicts, and failed checks arrive at once); IDLE reminders at 10m, 30m, 1h, 3h, then daily; WATCH EXPIRED and exit 0 after 3h with nothing but IDLE.\"\n },\n {\n \"name\": \"--until-event\",\n \"required\": false,\n \"description\": \"Wait for the first event in defaults.watch.untilEvent.kinds (agent.report, agent.blocked, panel.exited, pane.gone), print that one line, and exit 0. Inside a Pane terminal the cursor defaults to until-<panel id>, so a re-run resumes where the last stopped. A RESET and the caller's own panel never end the wait. With --timeout-ms, exit 124 with WATCH TIMEOUT when the deadline passes. Cannot be combined with --follow or --self-test.\"\n },\n {\n \"name\": \"--session\",\n \"required\": false,\n \"value\": \"<id|name>\",\n \"description\": \"Follow every Pane associated with a named Session (id or exact name). The daemon re-reads membership on every read, so a Pane associated after arming is included and a detached or archived Pane drops out without a re-arm. The Session's own orchestrator panels are never reported. Cannot be combined with --pane or --all-managed; an unknown Session fails.\"\n },\n {\n \"name\": \"--pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Limit to a Pane; repeatable. Default all managed panes. Cannot be combined with --session.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane; repeatable.\"\n },\n {\n \"name\": \"--repo\",\n \"required\": false,\n \"value\": \"<selector>\",\n \"description\": \"Limit to a saved repository (active, id, path, or name). Default all repositories.\"\n },\n {\n \"name\": \"--name-contains\",\n \"required\": false,\n \"value\": \"<text>\",\n \"description\": \"Limit by Pane name substring.\"\n },\n {\n \"name\": \"--include-shells\",\n \"required\": false,\n \"description\": \"Include plain shell panels. Default: agent panels only. A report (agent.report) arrives from any panel either way.\"\n },\n {\n \"name\": \"--kinds\",\n \"required\": false,\n \"value\": \"<kind,...>\",\n \"description\": \"Replace the default event kinds: agent.ready, agent.busy, agent.blocked, agent.unknown, agent.idle, pane.created, pane.gone, panel.exited, panel.stopped, panel.paused, panel.unpaused, agent.report, pane.associated, pane.detached, pr.conflicted, pr.checks, pr.merged. Opt-in only, never in a default: agent.report.missing, and Autopilot's item.created, item.answered, session.created, session.archived, orchestrator.turn_ended. Defaults: defaults.watch.follow.kinds under --follow, defaults.watch.untilEvent.kinds under --until-event, every kind for a one-shot read.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"One JSON object per line. Control lines use kinds _ok, _heartbeat, _reconnected, _error, _reset, _dropped, _timeout, and _expired.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"required\": false,\n \"description\": \"Name the cursor (at-least-once delivery): 1-128 letters, numbers, dots, underscores, or hyphens. Default under --follow: session-<uuid> with --session, else this panel (PANE_PANEL_ID, shortened to panel-<sha256 prefix> when longer than 64 characters); under --until-event: until-<panel id>; otherwise anonymous.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"required\": false,\n \"description\": \"Use an explicit generation cursor instead of a name. Cannot be combined with --as or --follow.\"\n },\n {\n \"name\": \"--from\",\n \"required\": false,\n \"value\": \"<now|earliest>\",\n \"description\": \"Starting point for a new named cursor. Default now (silent baseline); earliest replays the journal.\"\n },\n {\n \"name\": \"--self-test\",\n \"required\": false,\n \"description\": \"Check the daemon watch path without moving any cursor, print WATCH OK, and exit 0.\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long a panel must stay quiet before READY. Default 3m. A BUSY, BLOCKED, UNKNOWN, EXIT, or GONE inside the window cancels it. 0 turns it off.\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long before BLOCKED is printed; an in-pane answer inside it cancels the line. Default 30s. 0 turns it off.\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: batch READY, IDLE, NEW, GONE, EXIT, and UNKNOWN lines at most once per interval; REPORT, BLOCKED, pr.conflicted, pr.base_changed, pr.base_deleted, and failed pr.checks arrive at once and carry the held lines. Default 10m. 0 turns batching off.\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: the first IDLE reminder after a READY panel stays idle. Default 10m. Later reminders back off to 30m, 1h, 3h, then daily. 0 turns IDLE off.\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: end the watch with WATCH EXPIRED and exit 0 after this long with no events but IDLE; any other event restarts the clock. Default 3h. 0 never expires.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: print a HEARTBEAT line this often as proof of life. Default off; a monitor that wakes an agent should leave it off.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"required\": false,\n \"value\": \"<duration>\",\n \"description\": \"Per-request wait in milliseconds; 0 reads once without waiting. Default 60000, capped at 120000 (the follow loop re-issues requests). With --until-event it is the deadline for the whole wait (exit 124 with WATCH TIMEOUT). Accepts ms/s/m/h units; bare values are milliseconds. 0/off is a nonblocking read only, and is rejected with --follow or --until-event.\"\n },\n {\n \"name\": \"--limit\",\n \"required\": false,\n \"value\": \"<count>\",\n \"description\": \"Maximum entries per response. Default 256.\"\n },\n {\n \"name\": \"--ack-now\",\n \"required\": false,\n \"description\": \"At-most-once named-cursor delivery (advance before you act). Default at-least-once.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"required\": false,\n \"description\": \"Include up to 120 characters of unsubmitted composer text (typed text only; prompt suggestions and placeholders excluded) in JSON entries. Default off: under --follow, JSON entries carry only heldInputPresent: true.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"required\": false,\n \"description\": \"Disable redacted held-input detection under --follow: no STUCK lines, and no heldInputPresent in JSON.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\",\n \"required\": false\n }\n ],\n \"examples\": [\n \"runpane watch --self-test\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane watch --follow --json --kinds agent.report,agent.blocked\"\n ],\n \"notes\": [\n \"Every default is in the contract under defaults.watch: follow (what --follow does) and untilEvent (what --until-event waits for). An override flag replaces one value; nothing else needs remembering.\",\n \"Arming: wait for WATCH OK before relying on a --follow monitor. It prints once when the watch is armed. Run the monitor under your harness's background monitor (one line = one notification).\",\n \"Ending: WATCH EXPIRED (exit 0) means nothing happened for --expire-after: say so, and arm a new watch with the next dispatch. A non-zero exit or a WATCH ERROR line means it failed: tell the user and re-arm once; if it dies again, file runpane doctor --report. If the message says the cursor was superseded, keep the replacement monitor and leave the old one stopped. Judge a watch by these lines, never by silence.\",\n \"--until-event prints exactly one line: the event (exit 0), WATCH TIMEOUT (exit 124, only with --timeout-ms), or WATCH ERROR (exit 2).\",\n \"STUCK means real unsubmitted composer text. An agent prompt suggestion (for example a grey Try \\\"...\\\" hint) never counts.\",\n \"Cadence state is held per named consumer. Without numeric --since, follow uses an explicit --as or assigns a Session, panel, or follow-<pid> name. With numeric --since it has no name and bypasses cadence; --as cannot be combined with --since. Held lines survive a reconnect of the same consumer; a changed filter re-delivers them under the new filter.\",\n \"Journal loss is surfaced through reset and dropped metadata.\",\n \"The daemon treats omitted idleAfterMs as disabled so older clients never receive agent.idle unexpectedly.\",\n \"Control lines are not journal entries. Lines text and JSON kind: WATCH OK (_ok, once when armed), HEARTBEAT (_heartbeat, only with --heartbeat), WATCH RECONNECTED (_reconnected, after a retried daemon error), WATCH ERROR (_error; exit 2 unless --follow or --until-event retries it), RESET (_reset, the journal restarted or the cursor fell behind; a fresh baseline follows), DROPPED (_dropped, entries lost to journal truncation), WATCH TIMEOUT (_timeout, --until-event deadline; exit 124), and WATCH EXPIRED (_expired, --expire-after passed; exit 0). Under --until-event, only _timeout and _error print.\",\n \"Under --follow, JSON entries for panels holding unsubmitted composer text carry heldInputPresent: true, the JSON equivalent of the STUCK line. Only typed composer text counts, the same reading as panels screen composer.hasUndeliveredText; an agent's dim or grey prompt suggestion or placeholder does not. --no-held-input turns this off.\",\n \"--session <id|name> scopes the watch to a named Session. The daemon resolves the Session once per request and its associated Panes on every read, so sessions associate and sessions detach take effect without a re-arm; archived and detached Panes drop out, and the Session's own orchestrator panels are excluded. Cadence state is keyed by the Session, not its current Panes, so membership changes keep held lines. A detached Pane's held lines are dropped.\",\n \"Under --session, pane.associated (JOINED <pane-name> pane <pane-id> session <session-id>) and pane.detached (LEFT ...) report membership changes; list them in --kinds next to the agent kinds. Other consumers receive them only when --kinds lists them, so older clients never see an unknown kind.\",\n \"PR watching: the daemon batches up to 25 direct GraphQL fingerprints per host every ~2 minutes. Full details are read on change or a 30-minute backstop. Existing Session branch discovery remains; standalone Panes register with pr watch --url <PR URL> [--head <full-sha>]. Immediate failures, required-check-aware success, head/review/comment/closed/merged events and GitHub outage/recovery events share durable baselines and the journal. A head pin prevents stale success, empty/partial checks never pass, and merge status BLOCKED/UNKNOWN conservatively withholds success. Rate limits honor server waits and reserve 10% for interactive use. Eight other errors or ten comment-only updates stop the watch with pr.stopped. See pr watch/unwatch/list.\",\n \"A daemon that predates --session returns no session field; runpane then fails with WATCH ERROR instead of watching every Pane.\",\n \"Replay: after a reset (RESET/_reset), the baseline restates current state. In JSON, those entries carry replay: true (and changedWhileAway: true after an epoch change). A replayed agent.ready is never READY: re-read runpane sessions overview instead of acting on it. Lines mode prints only CHANGED for them.\",\n \"agent.report (REPORT <pane-name> pane <pane-id> panel <panel-id> <state> [pr#<n>] [<head>][: <question>]) is a worker's `runpane report`. It is in the default kinds of --follow and --until-event, and arrives from any panel: a shell tab or a wrapper launch whose agent is not yet detected included. It bypasses --min-interval, and orchestrators treat it as the completion signal; a READY without a report means look, and maybe nudge.\",\n \"Never pipe --follow into grep -m1, head, or any other exit-on-match reader: it does not exit when its reader does until its next write. To wait for one event, use --until-event.\",\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"--follow has one default cadence: READY 3m, BLOCKED 30s, non-urgent batches 10m, IDLE after 10m with backoff, expiry 3h, heartbeats off. Individual duration flags override it. --present and --profile were removed; use --ready-settle, --blocked-settle, and --min-interval for timing overrides.\",\n \"--until-event delivers matching events immediately without follow settling or batching. A harness that consumes a named follow stream gets its cadence; a process-exit waiter keeps immediate single-event semantics. --follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"Autopilot's kinds reach only watches that list them in --kinds (a Session scope alone does not): item.created and item.answered when an approval or question starts or stops waiting on the person (ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>; JSON itemId and reason), session.created and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>; paneId is the Session's own Pane), and orchestrator.turn_ended when a Session orchestrator goes from working to idle (TURN ENDED ...). Autopilot waits with `runpane watch --as autopilot --kinds item.created,item.answered,session.created,session.archived,orchestrator.turn_ended --until-event --json`.\"\n ]\n },\n \"panes git-status\": {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"details\": \"Use this to check whether a Pane has work to commit, push, or rebase before archiving it or opening a PR.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes git-status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes commit\": {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"details\": \"Use this to commit a Pane's work the way the app's commit action does. The message needs a title line.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes push\": {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"details\": \"Use this to publish a Pane's commits, the way the app's push action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes push --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes pull\": {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"details\": \"Use this to bring remote commits into a Pane, the way the app's pull action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pull --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes rebase-main\": {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"details\": \"Use this to bring main's changes into a Pane. It rewrites the Pane branch history; conflicts are reported, not resolved.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rebase-main --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes restore\": {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"details\": \"Use this to undo `panes archive`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes restore --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes squash-rebase\": {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"details\": \"Use this to land a Pane's work on the repository's main branch the way the app's squash-and-rebase action does. It rewrites the Pane branch and updates main.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash\": {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"details\": \"Use this to set aside work in progress, the way the app's stash action does. `panes stash-pop` brings it back.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash-pop\": {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"details\": \"Use this to bring back work set aside with `panes stash`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash-pop --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes soft-reset\": {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"details\": \"Use this to reword or regroup the latest commit, the way the app's soft reset action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes soft-reset --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes fetch\": {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"details\": \"Use this to refresh remote branches before checking git status or rebasing.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes fetch --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes run-script\": {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"details\": \"Use this to start the dev server or app for a Pane, the way the app's Run button does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes run-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes stop-script\": {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"details\": \"Use this to stop what `panes run-script` started.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stop-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes move\": {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"details\": \"Use this to organize Panes. Folder ids come from `runpane folders list`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<folder-id>\",\n \"required\": true,\n \"description\": \"Folder id from folders list.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"folders list\": {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"details\": \"Use this to find folder ids for `panes move`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders list --repo <repo-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"folders create\": {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"details\": \"Use this to add a folder for grouping Panes.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Folder name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"links create\": {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"details\": \"Use this to hand the user a clickable link. It only builds the URL; opening it navigates and never changes Pane state.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel inside the Pane to select.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": false,\n \"description\": \"Repository id from repos list.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<session-id>\",\n \"required\": false,\n \"description\": \"Session id or name from sessions list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane links create --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"notes\": [\n \"Links look like `pane://open?pane=<pane-id>&panel=<panel-id>`. Give exactly one of --pane, --repo, or --session.\"\n ]\n },\n \"links open\": {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"details\": \"Use this to navigate the app to a Pane, panel, repository, or Session. It raises the Pane window, so run it only when the user asked.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--url\",\n \"value\": \"<pane-url>\",\n \"required\": true,\n \"description\": \"A pane://open link from links create.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane links open --url \\\"pane://open?pane=<pane-id>\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Opening a link never changes Pane state; it selects what the link names.\"\n ]\n },\n \"docs search\": {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"details\": \"Use this before guessing how Pane works. Results are short excerpts with a path; read the full text with `runpane docs read`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"Words to search for.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum results; defaults to 5.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs search --query \\\"archive a pane\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"notes\": [\n \"Works offline. Skills are found in the Pane data directory when the Pane app has installed them.\"\n ]\n },\n \"docs read\": {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"details\": \"Use this with a path from `runpane docs search`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Path from docs search, such as docs/PANE_MCP.md.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs read --doc docs/PANE_MCP.md --json\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"notes\": [\n \"Paths come from docs search: `docs/<file>.md`, `skills/<name>/SKILL.md`, `help/<topic>`, or `command/<name>`.\"\n ]\n },\n \"agents start\": {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. For work that already has a Pane, pass --pane <id>: the agent opens as a new tab sharing that Pane's worktree and branch. Only when the work's branch has no Pane yet, pass --repo and --name to create one. It returns the pane and panel ids and a pane:// link to hand the user.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Existing Pane to add the agent to as a new tab. Use this for any work that already has a Pane.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Name for the new Pane. With --repo, only for work whose branch has no Pane yet.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<task>\",\n \"required\": true,\n \"description\": \"The task to send once the agent is ready.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch to start the worktree from.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"notes\": [\n \"Creates the Pane in the background (no focus change), pinned, and waits for the agent to be ready before sending the prompt.\",\n \"Check on it with `runpane agents status`; send follow-ups with `runpane agents send`.\"\n ]\n },\n \"agents status\": {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"details\": \"Use this to see how an agent started with `agents start` is doing, or whether it needs input.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Screen lines to return; defaults to 40.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"notes\": [\n \"`status` is `working`, `ready`, `blocked`, `idle`, `exited`, or `unknown`. `blocked` means the agent is waiting on a person.\"\n ]\n },\n \"agents send\": {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"details\": \"Use this to give an agent its next instruction. To answer a worker that still owes a report, add `--no-expect-report`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"The message to submit.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report, and Pane keeps waiting for that report.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane agents send --pane <pane-id> --text \\\"$(cat review-notes.md)\\\" --as-file-pointer --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"notes\": [\n \"`delivered` is true when the agent took the message or queued it for after its current turn; `delivery` says which (`taken` or `queued`) and how Pane knows (`transcript` or `screen`). Never resend a delivered message.\",\n \"It types the text, then queues with Tab when Codex is working or submits with Enter when ready. It is for messages, not keys. To answer a menu, use `runpane panels input --panel <panel-id> --keys down,enter --yes`, then check the screen with `runpane agents status`.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\"\n ]\n },\n \"report\": {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"details\": \"Run this as a delegated worker when you finish, fail, or get blocked, as the last step of your task. The orchestrator receives it as an agent.report watch event and treats it as the completion signal. It prints whether anyone was waiting for it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"required\": true,\n \"description\": \"ready: work is ready for review; done: finished with nothing to review; blocked: needs an answer; failed: could not finish.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"required\": false,\n \"description\": \"Pull request number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"required\": false,\n \"description\": \"Head commit the report is about, 7-40 hex characters.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the summary from a file or stdin; the absolute path is kept as summaryPath.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What you need answered; required with --state blocked.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id when not running inside a Pane terminal (PANE_SESSION_ID).\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id when not running inside a Pane terminal (PANE_PANEL_ID).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane report --state blocked --question \\\"Should the migration drop the old column?\\\"\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"notes\": [\n \"Inside a Pane terminal the report is for the current panel (PANE_SESSION_ID and PANE_PANEL_ID). Explicit --panel (with an optional --pane) wins; with neither, the command fails with a clear error.\",\n \"Each report replaces the panel's previous one and survives a Pane restart.\",\n \"agent.report is in the default kinds of `runpane watch --follow` and `--until-event`, and arrives from any panel, a shell tab included. REPORT lines skip the --min-interval batch.\",\n \"The output says who received it: \\\"Delivered to N waiting watcher(s).\\\" counts watches waiting at that moment (liveWatchers in JSON). With no Session and nobody waiting it adds that the report is stored on the panel (runpane agents status --panel <id>) and that a named-cursor watch (runpane watch --as <name> --kinds agent.report) still receives it when it next waits. Zero is not a failure; the exit code is 0 either way.\",\n \"With --state blocked and --question, the person sees an answerable card at once; it rings once the orchestrator relays it or after 10 minutes. The answer reaches this tab as `answer: Q-n → <answer> (by you)`. `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too.\"\n ]\n },\n \"sessions list\": {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"details\": \"List durable named orchestration Sessions.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"notes\": []\n },\n \"sessions create\": {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"details\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Create-time isPinned requires an updated Pane app. Older apps may ignore the field; use sessions pin after creation and read back the result.\",\n \"runtime appears only on WSL Sessions (\\\"wsl\\\", with wslDistribution, Windows only). A Session on the native host has no runtime field on any OS; \\\"windows\\\" is accepted on input as a legacy name for the native host and is not stored.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"Example: {\\\"name\\\": \\\"Release QA\\\", \\\"goal\\\": \\\"Ship 2.5\\\", \\\"decisions\\\": [\\\"Cut from main\\\"], \\\"blockers\\\": [], \\\"outputs\\\": [{\\\"label\\\": \\\"Release PR\\\", \\\"url\\\": \\\"https://github.com/greenfield-inc/Pane/pull/1\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"pull-request\\\"}]}\"\n ]\n },\n \"sessions get\": {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"details\": \"Read one durable named orchestration Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions update\": {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"details\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"archived: true also archives every Pane associated with the Session, as `panes archive --pane` would. A Pane that check refuses stays open (agent-busy, unsent-input, uncommitted-changes, unpushed-commits, uncommitted-and-unpushed, status-unknown), and so does one whose archive fails. The result's `panes` lists `archivedPaneIds`, `keptPanes` (with `code`) and `failedPanes` (with `error`); check a kept Pane with `panes archive --pane <id> --dry-run --json`. Undo is the person's: `panes restore`.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"update only: report is {\\\"summary\\\": non-empty string, \\\"status\\\": reported|verified, \\\"evidence\\\": [at least one link], \\\"reportedAt\\\": ISO 8601 date, \\\"provenance\\\": non-empty string}; report null clears it.\",\n \"Example: {\\\"expectedRevision\\\": 4, \\\"nextAction\\\": \\\"Merge after CI\\\", \\\"decisions\\\": [\\\"Cut from main\\\", \\\"Skip beta\\\"], \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"evidence\\\", \\\"provenance\\\": \\\"qa agent\\\"}], \\\"report\\\": {\\\"summary\\\": \\\"QA passed\\\", \\\"status\\\": \\\"verified\\\", \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\"}], \\\"reportedAt\\\": \\\"2026-10-14T09:31:00Z\\\", \\\"provenance\\\": \\\"qa agent\\\"}}\"\n ]\n },\n \"sessions set-agent\": {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"details\": \"Switch the durable terminal agent for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions associate\": {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"details\": \"Associate a user-visible Pane with a named Session. `panes create` and `panes adopt` already do this automatically when run inside a Session orchestrator; use this for existing Panes or after `--no-associate`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions detach\": {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"details\": \"Detach a Pane from a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions overview\": {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"details\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"notes\": [\n \"The result includes `locks`: named locks scoped to the Session or held by its Panes (see `runpane lock`).\",\n \"Each `panes[].panels[]` entry has `type`, the tab's panel type (`terminal`, `browser`, `editor`, `explorer`, `diff`, `logs`, `notes`, `dashboard` or `setup-tasks`). A Pane linked whole lists every tab, with `explorer` and `diff` last; a link that names tabs lists exactly those. Only terminals carry an agent `state`; other tabs report `unknown` and do not change the Session's `status`. A missing panel has no `type`, and daemons that predate it omit it.\",\n \"Each `panes[].panels[]` entry has `report`, that panel's latest `runpane report` (state, `pr`, `head`, `question`, `summary`, `summaryTruncated`, `summaryPath`, `reportedAt`). When the summary is a complete JSON object, `report.data` holds it exactly as the worker gave it; Pane never reads or renames its fields. A plain-text, array or scalar summary, invalid JSON, JSON nested more than 64 levels deep, or a truncated summary (`summaryTruncated`) has no `data`. A panel that never reported has no `report` but keeps `state` and `lastActivityAt`. `panes[].report` is still the newest report among the Pane's panels, with its `panelId`; daemons that predate per-panel reports omit `panels[].report`.\"\n ]\n },\n \"lock acquire\": {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"details\": \"Run this before using a resource that only one agent may use at a time, such as a shared test account, and release it when done. The owner is the calling Pane and panel ($PANE_SESSION_ID and $PANE_PANEL_ID, or --pane/--panel). The lock is scoped to the owner's Session when its Pane belongs to one, so two Sessions can each hold `testing-account`; otherwise it is global. It succeeds when the lock is free, expired, or already yours (which renews the TTL). Otherwise it returns ok:false with heldBy and expiresAt at once, or with --wait blocks in the daemon until the lock is released, expires, or its owner's panel exits or Pane is archived.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"required\": true,\n \"description\": \"How long the lock is held before it expires, such as 90s, 30m, or 2h; at most 24h. Acquire again before it runs out to renew.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Wait this long for the lock to come free. The CLI waits in daemon calls of up to 120 seconds each.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What the lock is for, shown to other agents. Required outside a Pane terminal, where it also names the owner.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock acquire --name staging-db --ttl 10m --json\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"notes\": [\n \"Exit status is 0 only when the lock is yours; ok:false (exit 1) means another owner holds it, named by heldBy until expiresAt.\",\n \"Release the lock as soon as you are done with the resource: `runpane lock release --name <name>`. If you interrupt a waiting acquire, the lock can still be granted to you for up to two minutes; release it or let the TTL expire.\",\n \"Pane releases the lock when its TTL runs out, when the owner panel exits, or when the owner Pane is archived. Quitting Pane does not release it; locks survive a restart.\"\n ]\n },\n \"lock release\": {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"details\": \"Only the owner can release a lock. --force releases another owner's lock, for example a stuck worker's; use --session to name a Session-scoped lock from outside that Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Release the lock even though another owner holds it.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session whose lock to release; defaults to the caller's Session, or global.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Outside a Pane terminal, the note the lock was acquired with.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock release --name testing-account --json\",\n \"runpane lock release --name testing-account --session \\\"<session-id-or-name>\\\" --force --json\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"notes\": [\n \"released:false with ok:true means no such lock was held. ok:false with reason not-owner (exit 1) means another owner holds it.\"\n ]\n },\n \"lock list\": {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"details\": \"List every held lock, or with --session the locks scoped to that Session plus those its Panes hold. `runpane sessions overview` includes the same Session locks.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock list --json\",\n \"runpane lock list --session \\\"<session-id-or-name>\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"notes\": []\n },\n \"workspace list\": {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"details\": \"Shows this machine's workspace state, then each other Mac, Windows, and Linux machine on your tailnet with its OS, online state, and shell. Your own machines come first; other people's machines carry their owner's login and answer only when their owner lets you in.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace list --json\"\n ],\n \"notes\": [\n \"A machine has joined when Pane runs there with workspaces on; it answers on https://<machine>.<tailnet>.ts.net:8443.\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n \"workspace enable\": {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"details\": \"Serves this Pane's daemon with tailscale serve on port 8443 in HTTP proxy mode. Only your own Tailscale login is accepted unless you set Who can connect to Everyone on tailnet; nothing is opened to the internet.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace enable\"\n ],\n \"notes\": [\n \"Workspaces are on by default when Tailscale is installed and signed in; this undoes runpane workspace disable.\"\n ]\n },\n \"workspace disable\": {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"details\": \"Turns workspaces off for this machine until runpane workspace enable. The remote daemon on port 443 and pairing codes are unaffected.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace disable\"\n ],\n \"notes\": [\n \"Other machines can no longer reach this one; this machine can still reach theirs.\"\n ]\n },\n \"workspace read\": {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"details\": \"Reads the same way on every OS. On Windows, /mnt/c/... reaches C:\\\\... and WSL paths such as /home/<user>/... are read through \\\\\\\\wsl.localhost\\\\<distro>\\\\. Directories list their entries.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form; ~ is that machine's home.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox read 'C:\\\\Users\\\\me\\\\.pane\\\\plans\\\\topic\\\\index.html'\",\n \"runpane workspace read /mnt/c/Users/me/notes.md\"\n ],\n \"notes\": [\n \"--json returns content with encoding utf8 or base64.\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n \"workspace write\": {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"details\": \"Takes the same path forms as workspace read. The content replaces the file.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text that replaces the file; an empty string empties it. Without it, the CLI writes stdin. The MCP tool requires it.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"echo done | runpane workspace parsa-devbox write '~/notes/status.md'\"\n ],\n \"notes\": [\n \"Binary stdin is sent as base64 and written byte for byte.\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n \"workspace exec\": {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"details\": \"Runs in the shell Pane uses for terminals on that machine (PowerShell or Git Bash on Windows, the login shell elsewhere), with that Pane's runpane first on PATH. The exit code becomes runpane's exit code. Without --, the command starts at the first word that is not an exec option. Without <machine>, a --cwd that cannot exist here picks the machine.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to let a --cwd that cannot exist here pick the one joined machine it fits.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Working directory on that machine, in any path form; defaults to its home.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Stop the command after this long (default 120000, at most 540000).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command line, run by that machine's shell.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox exec --json -- git -C C:\\\\src\\\\app status --short\"\n ],\n \"notes\": [\n \"This is SSH-level power: a local agent's sandbox and permission prompts see only runpane workspace exec, not what runs on the other machine.\",\n \"Pass the command as one quoted string; several words after -- are joined with spaces, so their own quoting is lost.\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n \"handoff\": {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"details\": \"Two steps. The agent writes the note, because only it has the context: run `runpane handoff --template > ~/handoff.md` (outside the checkout) and fill in every section (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state), writing \\\"None\\\" where one does not apply. Then `runpane handoff \\\"<destination>\\\" --note-file ~/handoff.md` moves it: it rejects a note with a missing or empty section, refuses an unpushed branch unless --push, finds the destination's saved repository with the same remote, fetches the branch there, writes the note to `~/.pane/handoffs/` on that machine with its origin in the front matter and receiver instructions appended, and starts the agent in a new Pane branched from your branch. When you run in a Pane tab and the receiver is your own agent, it carries your session instead of starting fresh: another machine gets a copy of the session's files in its agent store and resumes the same id; this machine resumes a fork. The receiver starts with no prompt, and the CLI checks it resumed and shows the session's last message, then tells it where the session moved. The receiver verifies the head commit, continues from Next steps, pushes to your branch, and reports back to your panel with `runpane workspace <your machine> panels submit`. Run from a Session's terminal (its folder is outside git), handoff moves the Session instead: it skips git, writes the note to the other machine the same way, and creates a Session there with the same name, goal and agent (numbered \\\"<name> 2\\\" if the name is taken), whose context and next action point at the note; a same-agent Session also gets your conversation's files and resumes it when first opened. The sender Session stays as it is; the new one waits for your first message. A Session cannot be handed off to the machine it is on.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<destination>\",\n \"required\": false,\n \"description\": \"Freeform text naming the agent (claude, codex, cursor), and optionally the model, the effort, and the machine: a Tailscale machine name or unique prefix (list them with `runpane workspace list`), or \\\"this machine\\\". Required unless --agent is given. Examples: \\\"codex gpt-5 high on parsas-macbook-pro\\\", \\\"claude opus on parsa-devbox\\\", \\\"cursor on this machine\\\".\"\n },\n {\n \"name\": \"--template\",\n \"required\": false,\n \"description\": \"Print the note template, with this checkout's (or Session's) origin and git state in its front matter, and exit.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"The note to check and send; required unless --template or --dry-run. Without it, --dry-run checks only the destination and git state.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Destination machine; overrides the text.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Receiving agent; overrides the text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"required\": false,\n \"description\": \"Model for the receiving agent, such as opus, sonnet, gpt-5, or claude-opus-5-5; overrides the text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"required\": false,\n \"description\": \"minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Saved repository on the destination. Defaults to the one with a remote for this repository.\"\n },\n {\n \"name\": \"--push\",\n \"required\": false,\n \"description\": \"Commit uncommitted work (except the note) as WIP and push the branch before sending; never forces. Without it, an unpushed branch stops the handoff.\"\n },\n {\n \"name\": \"--note-only\",\n \"required\": false,\n \"description\": \"Start a fresh agent with the note instead of carrying your own session. Without it, a handoff to the same agent carries and resumes your session.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check destination daemon reachability and resolve its saved repository name, path, environment and OS; print the note check and git state without committing, pushing, fetching, writing notes or creating a Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane handoff --template > ~/handoff.md\",\n \"runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \"runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run --json\",\n \"runpane handoff --machine build-server --agent cursor --note-file ~/handoff.md\"\n ],\n \"notes\": [\n \"Another machine is reached through runpane workspace (Tailscale); it needs Pane running there with workspaces enabled and the repository saved. Repository listing and Pane creation call the reached daemon directly; no global destination CLI installation is required.\",\n \"Destination preflight runs before sender commit/push. Inside WSL, an absent or refused local Linux daemon can fall back to its Windows host through reachable Windows Tailscale and enabled workspaces; explicit Pane directory selection keeps its instance. Select a native Windows saved repository; WSL saved repositories are rejected before sending.\",\n \"A successful handoff requires initialInput.verifiedSubmitted and delivery taken or queued without blocked/error evidence. Unverified delivery exits nonzero with the created Pane, panel and a panels screen inspection command; inspect before retrying.\",\n \"From a Session's terminal (PANE_ORCHESTRATION_SESSION_ID set, folder outside git) the receiver is a Session, not a Pane: it needs only runpane:sessions:create and runpane:machine:write on the destination, pushes nothing, and refuses this machine before sending. JSON output then has receiver.route \\\"session\\\", session {id, name, machine} and no git field; a dry run reports the name the Session would get.\",\n \"Write the note for a reader with no context: quote errors and commands exactly, and record approaches that failed so they are not retried. Leave the front matter to the CLI.\",\n \"Model and effort become agent flags: claude --model/--effort, codex -m and -c model_reasoning_effort, cursor-agent --model. Cursor effort is not supported and is rejected before side effects.\",\n \"Run it from the checkout being handed off. On another machine the receiver works in a new Pane; on this machine, from the Pane that owns this checkout, it works in a new tab of that Pane (the result's receiver.route and receiver.reason say which). Either way it pushes back to your branch, so stop changing that branch yourself.\",\n \"JSON continuity: { carried, result: continued | inconclusive | not-continued | fresh-agent, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }; a dry run sets carried, sessionId, fork, files and bytes and no result. inconclusive exits 0 and next says to ask the receiver \\\"What were you last working on? Answer in one or two sentences.\\\"; not-continued exits 1, keeps the receiver and gives the --note-only command for the person to choose. After continued or inconclusive, the sender ends its turn and makes no more changes on the branch. From a Session the result is inconclusive until the new Session is opened. A Cursor session is carried only to another machine (best effort); on this machine the result is not-continued, since a copied Cursor chat still writes to the original. The destination's agent store is assumed at its default location (~/.claude, ~/.codex, ~/.cursor); a copy already there that holds history the sender's lacks, or that an agent there is running, is never overwritten (not-continued). checks.originalAppended is set when the running sender's own turn appended to its transcript during the fork; every byte it held before is unchanged. A destination Pane too old to resume a carried session is refused before anything is sent (ERR_HANDOFF_RESUME_UNSUPPORTED).\"\n ]\n },\n \"sessions unpin\": {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pin\": {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pause\": {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"details\": \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived; a chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`, until `sessions resume` resumes them. A Session reads as paused (`sessions get` `session.paused`) while its chat and all of its Panes are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pause --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"sessions resume\": {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"details\": \"Resumes the paused tabs of a Session's chat and Panes. An archived Session is refused. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions resume --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"pr watch\": {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"details\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<full-sha>\",\n \"required\": false,\n \"description\": \"Pin success to this 40-character SHA; otherwise pin the first observed head.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr unwatch\": {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"details\": \"Remove a Pane’s explicit PR registration.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr list\": {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"details\": \"List PR registrations and stopped watches.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"inbox\": {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"details\": \"An orchestrator runs it when it starts or resumes, to catch reports, missing reports, questions and answers that arrived while it was down. Pane never adds the inbox to the orchestrator's turns.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session; defaults to PANE_ORCHESTRATION_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox --session \\\"$PANE_ORCHESTRATION_SESSION_ID\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"notes\": [\n \"Items: report (agent.report), report.missing (agent.report.missing), and blocked (agent.blocked, with the prompt's screen lines). Reports coalesce per panel, blockers clear when the panel moves on, and each Session keeps at most 200 items until acknowledged or superseded. A journal replay watermark recovers committed outcomes after restart within journal retention.\",\n \"A newer report replaces the panel's older report or missing report.\",\n \"`answers` lists what the Session's workers were told, kept 7 days. One with no `decision` still needs recording: `runpane inbox ack <id> --decision <D-n>`. An older Pane leaves `answers` out.\"\n ]\n },\n \"inbox ack\": {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"details\": \"Acknowledge each item once you have acted on it, so the hook stops repeating it. With --decision, also record the D-n of an answer the worker already got.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<item-id>...\",\n \"required\": false,\n \"description\": \"Item ids from `runpane inbox`.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"required\": false,\n \"description\": \"Record this D-n on each named item, such as D-147. Not with --all.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Only clear items of this Session; required with --all.\"\n },\n {\n \"name\": \"--all\",\n \"required\": false,\n \"description\": \"Clear every item of the Session.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox ack in-1a2b3c4d --json\",\n \"runpane inbox ack in-1a2b3c4d --decision D-147 --json\"\n ],\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"notes\": [\n \"Unknown ids are reported under `unknown`; acknowledging twice is harmless.\",\n \"An id already cleared whose answer has no decision still records it.\"\n ]\n },\n \"loops list\": {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"details\": \"Lists every loop: its id, target (projectId for a repo, or orchestrationSessionId for a Session), cron and scheduleText, enabled, nextRunAtMs (null while paused), and its newest runs. A run's sessionId is its Pane (null for a Session loop's runs); read its progress with `runpane agents status --pane <id>`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops list --json\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"notes\": [\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops create\": {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"With --repo, each run is a new Pane in a fresh worktree branched from the repo's default base branch, started like `runpane panes create` with the agent or command and prompt given here. With --session, each run sends the prompt to that Session's agent instead of creating a Pane; a Session loop needs --agent and --prompt and takes no --tool-command. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": true,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--paused\",\n \"required\": false,\n \"description\": \"Create the loop paused.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops create --repo <repo> --name dependency-check --cron \\\"0 9 * * 1\\\" --tool-command \\\"pnpm test\\\" --yes --json\",\n \"runpane loops create --session new --name morning-triage --cron \\\"0 9 * * 1-5\\\" --agent claude --prompt \\\"Triage the new issues\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": [\n \"Saving rejects a cron whose runs fall less than 60 minutes apart. Check one first with --dry-run.\"\n ]\n },\n \"loops update\": {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"Reads the loop, replaces the values you pass, and saves it. Passing --agent clears --tool-command and the other way round; --repo and --session replace each other the same way. Pause and resume with `loops pause` and `loops resume`. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": false,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops update --loop <loop-id> --cron \\\"0 4 * * *\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": []\n },\n \"loops run\": {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"details\": \"Starts a run now, as a new Pane or a message to the loop's Session, even while the loop is paused or another run is still going. The schedule stays the same, and the run counts toward the 10 kept runs.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops run --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": [\n \"The result is the loop; `runs[0]` is the new run, with `status` started or failed and `sessionId` its Pane (null for a Session loop).\",\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops pause\": {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"details\": \"A paused loop starts no scheduled runs. `loops run` still starts one by hand.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops pause --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops resume\": {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"details\": \"Runs resume at the next scheduled time. Runs missed while paused are not caught up.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops resume --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops delete\": {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"details\": \"Deletes the loop and its schedule.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops delete --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"notes\": []\n },\n \"notify\": {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"details\": \"Prefer this to asking in the terminal whenever you need the person: to pick between options (ui options), to approve or deny an action (ui approve), to type a short answer (ui ask), or to tell them something that needs no answer (ui info). Pane shows it on screen even while the person works in another app, and the call waits until they answer, however long that takes. info returns at once.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"required\": false,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer. info: an update that returns at once. Required unless resume, took or update.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"info\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The question or update in one line, about 80 characters; long titles wrap to 3 lines. Required unless resume, took or update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Why it matters, shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there unless questions: 1 to 9 choices. The answer is the chosen label. Say what each does in detail, and mark the one you recommend. With update, a reworded choice adds was, its old label.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only: a preformatted block, such as the command or SQL to approve, behind a disclosure.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only, with detail: the disclosure's label, such as \\\"Show the SQL\\\"; up to 40 characters. Defaults to \\\"Show details\\\".\",\n \"schema\": {\n \"type\": \"string\",\n \"maxLength\": 40\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui info only: one button that opens a URL.\",\n \"schema\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files, pages or approvals, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https. Another approval is {label, approval}, its id or Q-n.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"required\": false,\n \"description\": \"A Session orchestrator relaying a worker's question: the waiting item's id from `runpane inbox`. The same card gains these choices, context and links instead of a second card showing. ui and title are still required.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Autopilot's own item: the Session it is about, shown as its connected Session. Without it, the item links to Autopilot's chat.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, instead of choices: 2 to 4 questions asked at once, each with 1 to 9 choices and one pick. The result's answers holds one label per question; answer joins them with \\\" · \\\".\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n }\n }\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After a reply result or a `reply:` line: the approval's id or Q-n, with tookAs answer (record the reply as the answer) or question (it asked something; find out, then call update). Pass no question fields.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"required\": false,\n \"description\": \"With took: answer or question. On the command line it can follow the id instead (`--took Q-20 answer`).\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n }\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With took question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After you took a reply as a question and found out: the approval's id or Q-n. Saves a new version with any of title, context, choices, questions and links you pass (the rest stay), shows it as updated, and waits on it like any notify call. Needs asked and whatChanged; takes no ui.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: what changed, one line per change.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"The id from a waiting result: keep waiting on that question. Pass no other fields.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane notify --ui options --title \\\"Which database for the cache?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]' --json\",\n \"runpane notify --ui approve --title \\\"Drop the legacy_users table?\\\" --detail \\\"DROP TABLE legacy_users;\\\" --detail-label \\\"Show the SQL\\\" --json\",\n \"runpane notify --ui ask --title \\\"Which branch should I base this on?\\\" --suggestions '[\\\"main\\\",\\\"release\\\"]' --json\",\n \"runpane notify --ui info --title \\\"PR #812 is ready for review\\\" --action '{\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}' --json\",\n \"runpane notify --ui approve --title \\\"Ship the release notes?\\\" --links '[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR #812\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}]' --json\",\n \"runpane notify --relays in-1a2b3c4d --ui options --title \\\"Q-39: Run the script?\\\" --choices '[{\\\"label\\\":\\\"Run it\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]' --json\",\n \"runpane notify --ui options --title \\\"Ship 2.5 today: which parts go out?\\\" --questions '[{\\\"title\\\":\\\"Release notes\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Ship as written\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]},{\\\"title\\\":\\\"Intel Mac build\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Include it\\\"},{\\\"label\\\":\\\"Skip it\\\"}]}]' --json\",\n \"runpane notify --took Q-20 answer --json\",\n \"runpane notify --took Q-20 question --status \\\"Checking whether it can be undone\\\" --json\",\n \"runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: Settings > Search turns it off again\\\" --context \\\"Search takes 4 s; the index brings it under 100 ms. It can be undone from Settings.\\\" --json\",\n \"runpane notify --resume n-4f2a --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"Write the card the way the pane MCP server's instructions say: in plain words, what the work is for, what just happened, and what you need.\",\n \"Results: options returns {answer: <chosen label>, note?}, with questions also answers (one label per question); approve returns {approved: true|false, note?}; ask returns {answer}; info returns {delivered: \\\"screen\\\"|\\\"inbox\\\"}.\",\n \"A result of {reply, picks?, id, replyId, next} means the person typed a reply instead of only picking: it may answer the question or ask one. Read it, then call `runpane notify --took <replyId> answer` to record it as the answer, or `runpane notify --took <replyId> question` and find out, then `runpane notify --update <replyId> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with whatever changed, instead of asking again. With no open call the reply reaches your tab as a `reply:` line; `correction:` lines fix how it was taken. --took returns {took, id, next?}. Use its replyId rather than the Q-n: only this call was given it, so it works from outside a Pane tab too.\",\n \"Only the reply's reader may run took and update: the asker, the orchestrator that relayed it, or Autopilot. Anyone else gets ERR_NOTIFY_NOT_READER.\",\n \"A result of {waiting: true, id} means the question is still on screen and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting. After 15 minutes on this loop Pane gives up and returns unavailable.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now (Pane is not running, Notify is off, or the wait ended): ask in the terminal instead. It is a result, not an error.\",\n \"Inside a Pane terminal the question names its Pane and panel (PANE_SESSION_ID and PANE_PANEL_ID); elsewhere it names the working directory.\",\n \"An older Pane ignores links and shows the card without them.\",\n \"With relays the call waits on the worker's card as on any notify, and the answer also goes straight to the worker's tab as `answer: Q-n → <answer> (by you)`. Then record it: `runpane inbox ack <item> --decision <D-n>`.\",\n \"Invalid input, such as choices with ui ask, 10 choices, or ui with took or update, fails with a message naming the field, and nothing is shown.\"\n ]\n },\n \"autopilot status\": {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"details\": \"Autopilot (experimental) is one always-running agent above every Session. It is not a Session: its run lives in <PANE_DIR>/autopilot/. Read this first to know whether it is on, entitled, and running.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot status --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"notes\": [\n \"Works while Autopilot is off (enabled: false), so a caller can tell off from broken.\"\n ]\n },\n \"autopilot waiting\": {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"details\": \"Autopilot's queue, read from each machine's table with no copy: every item waiting on the person on every machine it can reach, filed under its Session (or not in one), this machine's first, with who asked, how long it has waited, whether it is on the stack, and whether it is stale or quiet. `offline` names saved machines it can't reach, and `cantSee` names the person's Tailscale machines not saved here.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot waiting --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"Its ids go straight to `runpane autopilot answer`, `close`, `rewrite` and `stack push --item`, whichever machine holds the item.\",\n \"A `quiet` item is a worker's question its orchestrator still has first call on; a `stale` one has waited 3 days and no longer rings.\",\n \"An older Pane leaves `offline` and `cantSee` out.\"\n ]\n },\n \"autopilot stack\": {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"details\": \"The stack holds entries in the order Autopilot put them; one is shown at a time and the next comes up when the person resolves it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off. A resolved entry carries doneAt.\"\n ]\n },\n \"autopilot stack push\": {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"details\": \"Give exactly one of --item, --file, --url, or --session. A new entry never replaces the one on screen; --next puts it right after it. runpane resolves relative file paths (the entry's and its tabs') against the caller's directory before sending.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"A document to show: HTML loads as a page, anything else as text.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"An http(s) page or running app to show.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"A Session whose overview to show.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The entry's title; defaults to the item's, file's, or Session's own.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Autopilot's one line about why it is here.\"\n },\n {\n \"name\": \"--tab\",\n \"value\": \"<path-or-url>\",\n \"required\": false,\n \"description\": \"An extra tab: a file path, or an http(s) URL. Repeatable on the CLI.\"\n },\n {\n \"name\": \"--next\",\n \"required\": false,\n \"description\": \"Put it right after the entry on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack push --item n-1a2b3c --note \\\"The release orchestrator wants a go\\\" --json\",\n \"runpane autopilot stack push --file docs/plan.html --tab https://localhost:5173 --next --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_PAUSED (the person paused Autopilot; wait for Go on), AUTOPILOT_STACK_INVALID (no such item, file, URL, or Session).\",\n \"The result says where the entry went: position of total, and shown when it is on screen now.\"\n ]\n },\n \"autopilot answer\": {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"details\": \"Give exactly one of --choice, --approve, --deny or --text, and --because. The machine that holds the item delivers the answer at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the person can Override it. Skill behavior, not a setting: answer only when the way forward is clear and the item relates to your work with the person, or when the person told you to handle approvals.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"required\": false,\n \"description\": \"The chosen label of an options item.\"\n },\n {\n \"name\": \"--approve\",\n \"required\": false,\n \"description\": \"Approve an approve item.\"\n },\n {\n \"name\": \"--deny\",\n \"required\": false,\n \"description\": \"Deny an approve item.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A typed answer.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A note for the asker, with --choice, --approve or --deny.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why, recorded on the item: a D-n, a document or the person's words.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot answer n-1a2b3c --choice \\\"Run it\\\" --because \\\"D-22\\\" --json\",\n \"runpane autopilot answer n-1a2b3c --approve --because \\\"The person said to ship it\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot close\": {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"details\": \"The machine that holds the item closes it, tells its asker, and records \\\"Closed by Autopilot: <why>\\\" on every device. The person can reopen it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why it no longer needs the person, recorded on the item.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot close n-1a2b3c --reason \\\"The PR it asks about merged yesterday\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot rewrite\": {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"details\": \"The old item reads \\\"Closed by Autopilot: rewritten as …\\\" and the new one \\\"Rewritten by Autopilot from Q-n\\\". The rewrite stays on the old item's machine, and its answer goes to the original asker. runpane resolves relative --links paths against the caller's directory; a file must exist on the item's machine.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask>\",\n \"required\": true,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"The question in one line.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Background the person can open under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there: 1 to 9 choices. The answer is the chosen label. Mark at most the one you recommend.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files or pages, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot rewrite n-1a2b3c --ui options --title \\\"Q-39: Run the import on the copy first?\\\" --choices '[{\\\"label\\\":\\\"Copy first\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Skip\\\"}]' --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"The result's id is the new item's. Put that one on the stack.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"This repository is used with [Pane](https://runpane.com). Drive it with the CLI or the `pane` MCP server.\",\n \"\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.\",\n \"\",\n \"When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \\\"<question>\\\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.\",\n \"\",\n \"CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.\",\n \"\",\n \"MCP: packaged Pane registers a stdio server named `pane` with Claude Code, Codex, and Cursor. Check the connection with `claude mcp list`, `codex mcp list`, or `agent mcp list`. Cursor may ask you to enable `pane` with `agent mcp enable pane`. If tools are missing, add it in the agent's MCP settings: Claude Code `claude mcp add --scope user pane -- npx --yes runpane@latest mcp`; Codex (`~/.codex/config.toml`) table `[mcp_servers.pane]` with `command = \\\"npx\\\"` and `args = [\\\"--yes\\\", \\\"runpane@latest\\\", \\\"mcp\\\"]`; Cursor (`~/.cursor/mcp.json`) uses `mcpServers.pane` with the same `npx` command and args; any other stdio client uses them too.\"\n ],\n \"notifyGuidance\": {\n \"rules\": [\n \"In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \\\"<question>\\\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.\",\n \"Ask only what you can't find out yourself.\",\n \"Put related questions in one card.\",\n \"Send `info` rarely, only for what the person must know now.\",\n \"Write the card as its Summary: a title of about 80 characters; context saying why it matters; each choice saying what it does, with one recommended; links to every file, page or approval it mentions.\",\n \"A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\\\"was\\\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.\",\n \"For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`.\"\n ],\n \"cardRule\": \"The person is juggling many tasks and has lost the thread. In plain words, say what the work is for (the outcome, not the file), what just happened, and what you need. For a decision, give the options and what each one means. Pane already shows which Pane and agent sent it.\",\n \"results\": \"`waiting` means call `notify` again with `resume`. `unavailable` means ask in the terminal; for a secret, ask the person to save it in Pane under Settings > Secrets.\",\n \"examples\": [\n {\n \"name\": \"Needs a decision\",\n \"ui\": \"approve\",\n \"bad\": [\n {\n \"title\": \"Approve the migration?\",\n \"context\": \"Ready to run 0042_add_index on prod. Proceed?\"\n }\n ],\n \"good\": {\n \"title\": \"Speed up slow search: run the database change?\",\n \"context\": \"Search in the app takes 4 s, and an index brings it under 100 ms. It changes the live database and can't easily be undone. Approve runs it now; Deny keeps it ready for later.\"\n }\n },\n {\n \"name\": \"Blocked\",\n \"ui\": \"options\",\n \"bad\": [\n {\n \"title\": \"Blocked\",\n \"context\": \"CI failing on macos-intel, hdiutil resource busy.\"\n }\n ],\n \"good\": {\n \"title\": \"The 2.5 release is stuck on a GitHub hiccup\",\n \"context\": \"The fixes you asked for pass everywhere except one Mac build machine, which errored. That's GitHub's machine, not our code.\",\n \"choices\": [\n \"Rerun it (about 15 min)\",\n \"Ship without the Intel Mac build\"\n ]\n }\n },\n {\n \"name\": \"Just so you know\",\n \"ui\": \"info\",\n \"bad\": [\n {\n \"title\": \"Done\",\n \"context\": \"Completed refactor of src/notify/queue.ts (head 06d310a).\"\n },\n {\n \"title\": \"PR #812 is ready\",\n \"context\": \"Tests pass.\"\n }\n ],\n \"good\": {\n \"title\": \"Questions from agents now wait until you answer\",\n \"context\": \"The fix you asked for this morning is done and tested. Next is your review: one click to merge.\"\n }\n },\n {\n \"name\": \"A question for ask\",\n \"ui\": \"ask\",\n \"bad\": [\n {\n \"title\": \"Which option?\",\n \"context\": \"1, 2 or 3?\"\n }\n ],\n \"good\": {\n \"title\": \"What should the release headline say?\",\n \"context\": \"You're writing the 2.5 release notes, and the headline is the first line people read. A few words is enough.\"\n }\n }\n ]\n }\n }\n}") +RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \"^22.23.4 || ^24.22.1 || ^26.12.1\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false,\n \"watch\": {\n \"follow\": {\n \"summary\": \"What `runpane watch --follow` does with no other flags: quiet, batched updates and expiry after 3 hours with nothing happening. Individual duration flags override the defaults.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.ready\",\n \"agent.blocked\",\n \"agent.idle\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"pane.gone\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\"\n ],\n \"readySettle\": \"3m\",\n \"blockedSettle\": \"30s\",\n \"minInterval\": \"10m\",\n \"idleAfter\": \"10m\",\n \"idleBackoff\": true,\n \"expireAfter\": \"3h\",\n \"heartbeat\": \"off\",\n \"includeShells\": false,\n \"heldInputDetection\": true\n },\n \"untilEvent\": {\n \"summary\": \"What `runpane watch --until-event` does with no other flags: waits for the first event worth waking an agent for, prints it immediately without follow settling or batching, and exits 0.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.blocked\",\n \"panel.exited\",\n \"pane.gone\"\n ]\n }\n }\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\",\n \"cursor\",\n \"opencode\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n },\n \"cursor\": {\n \"title\": \"Cursor\",\n \"command\": \"cursor-agent --force --trust\",\n \"description\": \"Open a Cursor Agent terminal tab and allow the initial input to drive the agent.\"\n },\n \"opencode\": {\n \"title\": \"OpenCode\",\n \"command\": \"opencode --auto\",\n \"description\": \"Open an OpenCode terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"terminalKeys\": {\n \"enter\": \"\\r\",\n \"escape\": \"\\u001b\",\n \"tab\": \"\\t\",\n \"backspace\": \"\",\n \"space\": \" \",\n \"up\": \"\\u001b[A\",\n \"down\": \"\\u001b[B\",\n \"right\": \"\\u001b[C\",\n \"left\": \"\\u001b[D\",\n \"ctrl-c\": \"\\u0003\",\n \"ctrl-d\": \"\\u0004\"\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true,\n \"localControl\": false\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true,\n \"localControl\": false\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update both Pane and the invoked supported CLI through an independent helper.\",\n \"usage\": [\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\"\n ],\n \"target\": \"client\",\n \"localControl\": false\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version [--pane-dir <path>]\",\n \"runpane --version\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, runpane-on-PATH, and remote setup diagnostics.\",\n \"openWorld\": true,\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"runpane doctor --fix [--json] [--pane-dir <path>]\",\n \"runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"usage\": [\n \"runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command <command> [--json]\"\n ],\n \"toolsets\": [\n \"admin\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"usage\": [\n \"runpane mcp [--toolsets <name,...>] [--read-only]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path <path> [--name <name>] [--json] [--yes] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"usage\": [\n \"runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"usage\": [\n \"runpane repos update --repo <selector> [--name <name>] [--build-script <script>] [--run-script <script>] [--archive-script <script>] [--worktree-folder <path>] [--open-ide-command <command>] [--system-prompt <text>] [--env <json>] [--env-remove <name,...>] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos rename --repo <selector> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"usage\": [\n \"runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"usage\": [\n \"runpane secrets set --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"localControl\": true\n },\n {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"usage\": [\n \"runpane secrets delete --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"usage\": [\n \"runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... [--pane-dir <path>] -- <command>...\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"usage\": [\n \"runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"usage\": [\n \"runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace state\",\n \"summary\": \"Read one workspace snapshot of every Pane and CLI panel.\",\n \"usage\": [\n \"runpane workspace state [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceStateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"usage\": [\n \"runpane workspace list [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"usage\": [\n \"runpane workspace enable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"usage\": [\n \"runpane workspace disable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"usage\": [\n \"runpane workspace [<machine>] read <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"usage\": [\n \"runpane workspace [<machine>] write <path> [--content <text>] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"usage\": [\n \"runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"usage\": [\n \"runpane handoff --template\",\n \"runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace agent and Pane transitions using a daemon-held cursor.\",\n \"usage\": [\n \"runpane watch [--follow|--until-event] [--session <id|name>|--pane <id>] [--json] [overrides]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceWaitRequest\",\n \"workspaceWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"usage\": [\n \"runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --from-json <path|-> [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane without changing the worktree.\",\n \"usage\": [\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [--resume <id>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\",\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"usage\": [\n \"runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes pin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"usage\": [\n \"runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"usage\": [\n \"runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"usage\": [\n \"runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels) on explicit user request.\",\n \"usage\": [\n \"runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent or terminal tab inside an existing Pane, sharing its worktree. The default for review, QA, fixes and any other agent on work that already has a Pane.\",\n \"usage\": [\n \"runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"usage\": [\n \"runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"localControl\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"usage\": [\n \"runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--source user|agent] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place, resuming its agent's conversation.\",\n \"usage\": [\n \"runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"usage\": [\n \"runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"usage\": [\n \"runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"usage\": [\n \"runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane panels conversation --hook [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"usage\": [\n \"runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"sessions:get-git-status\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"usage\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stage-and-commit\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"usage\": [\n \"runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-push\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"usage\": [\n \"runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-pull\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"usage\": [\n \"runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:rebase-main-into-worktree\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"usage\": [\n \"runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:restore\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"usage\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:squash-and-rebase-to-main\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash-pop\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"usage\": [\n \"runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-soft-reset\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"usage\": [\n \"runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-fetch\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"usage\": [\n \"runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:run-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"usage\": [\n \"runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:stop-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"usage\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"folders:move-session\",\n \"args\": [\n \"--pane\",\n \"--folder\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"usage\": [\n \"runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"folders:get-by-project\",\n \"args\": [\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"usage\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"folders:create\",\n \"args\": [\n \"--name\",\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"usage\": [\n \"runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \"runpane links create --repo <repo-id> [--json]\",\n \"runpane links create --session <session-id> [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"links\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"usage\": [\n \"runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"runpane:links:open\",\n \"args\": [\n \"--url\"\n ]\n },\n \"toolsets\": [\n \"links\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"usage\": [\n \"runpane docs search --query <text> [--limit <count>] [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"usage\": [\n \"runpane docs read --doc <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"usage\": [\n \"runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"usage\": [\n \"runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"usage\": [\n \"runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"usage\": [\n \"runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"usage\": [\n \"runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \"runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"runpane notify --resume <id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"usage\": [\n \"runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"usage\": [\n \"runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"usage\": [\n \"runpane autopilot status [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"usage\": [\n \"runpane autopilot waiting [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"usage\": [\n \"runpane autopilot stack [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"usage\": [\n \"runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"usage\": [\n \"runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"usage\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"usage\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"usage\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"usage\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"usage\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"usage\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"usage\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"usage\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"usage\": [\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"usage\": [\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"usage\": [\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"usage\": [\n \"runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"usage\": [\n \"runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"usage\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"usage\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"usage\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"usage\": [\n \"runpane loops list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"usage\": [\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"usage\": [\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"usage\": [\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"usage\": [\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"localControl\": true\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository, created/renamed pane session, named lock, or loop.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name. For a loop, the worktree and branch name template; defaults to loop-<name>.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"description\": \"Exact new branch name for the created worktree, slashes included. Must not exist yet; defaults to the worktree name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"aliases\": [\n \"--base\"\n ],\n \"description\": \"Ref the created worktree branches from. --base is an alias.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"description\": \"Top-level sidebar folder for an adopted pane.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<agent-session-id>\",\n \"description\": \"Agent session id to persist and resume in an adopted pane.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"description\": \"http(s) or file URL for panels open; pane:// link for links open.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"description\": \"File inside the Pane worktree to open; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"aliases\": [\n \"--prompt-file\"\n ],\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--attach\",\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a structured JSON request payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create/adopt --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated. On panels conversation it is instead what started the conversation: startup, resume, clear, or compact.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"description\": \"Use a persisted daemon-held workspace cursor.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"description\": \"Read workspace events after this generation.\"\n },\n {\n \"name\": \"--from\",\n \"value\": \"<now|earliest>\",\n \"description\": \"Choose where a new named cursor starts.\"\n },\n {\n \"name\": \"--kinds\",\n \"value\": \"<kind,...>\",\n \"description\": \"Limit workspace events to comma-separated kinds.\"\n },\n {\n \"name\": \"--name-contains\",\n \"value\": \"<text>\",\n \"description\": \"Limit workspace events to Pane names containing text.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane from workspace watch results; repeatable.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"description\": \"Print a HEARTBEAT line this often under --follow (a duration; default off).\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"description\": \"First IDLE reminder after a READY panel stays idle (a duration; default 10m under --follow; 0 turns IDLE off).\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"description\": \"End a --follow watch after this long with nothing but IDLE, printing WATCH EXPIRED and exiting 0 (a duration; default 3h; 0 never expires).\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Quiet time before READY under --follow (a duration; default 3m).\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Time before BLOCKED under --follow (a duration; default 30s).\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"description\": \"Batch window for non-urgent lines under --follow (a duration; default 10m).\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read diagnostic report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"description\": \"Named orchestration Session id or exact name.\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"description\": \"How long a named lock is held before it expires: a number with ms, s, m, or h (such as 30m); a bare number is milliseconds. At least 1s, at most 24h.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"description\": \"lock acquire: how long to wait for a held lock to come free; 0 (the default) returns at once. On panels submit, panels create and panes create, --wait takes no value: block until that panel reports, misses its report, waits on a prompt, or exits, then print one JSON result.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"description\": \"With --wait: give up after this long (30m, 2h; 0 waits without limit) and exit 124.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"description\": \"What a named lock is for. Outside a Pane terminal it also names the owner, so pass the same note to release it.\"\n },\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"description\": \"Five-field cron schedule in this machine's local time zone, such as \\\"0 3 * * *\\\". Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"description\": \"Commit message for panes commit.\"\n },\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"description\": \"Search words for docs search.\"\n },\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"description\": \"Doc path for docs read.\"\n },\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"description\": \"MCP toolsets for runpane mcp.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"description\": \"Named keys for panels input, such as down,enter.\"\n },\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"description\": \"Report state for runpane report.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"description\": \"Pull request number for runpane report.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"description\": \"Head commit (7-40 hex characters) for runpane report.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"description\": \"Report summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read the report summary from a file or stdin.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"description\": \"What a blocked worker needs answered; required with --state blocked.\"\n },\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"description\": \"The conversation (agent session) id a panel's agent is on, for runpane panels conversation.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"description\": \"Working directory on the target machine for workspace exec.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"description\": \"Handoff note to check and send; - reads stdin.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"description\": \"Handoff destination machine; overrides the machine in the destination text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"description\": \"Model for the receiving agent; overrides the destination text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"description\": \"Reasoning effort for the receiving agent (minimal, low, medium, high, xhigh, max); overrides the destination text.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository. Defaults to worktrees/ in the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME|json>\",\n \"description\": \"secrets exec: inject secret NAME as VAR; repeatable. repos update: a JSON array of project variables to add or replace by key.\"\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"description\": \"Pane secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"description\": \"Secrets to inject, comma-separated. Defaults to every secret in the project.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"description\": \"secrets request: why you need the secret, shown on the card. autopilot close: why it closes, recorded on the item.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"description\": \"notify: what to show: pick one of choices, approve or deny, type an answer, or an update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"description\": \"notify: background shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options: 1 to 9 choices as a JSON array of {label, detail?, recommended?}; with --update a reworded choice adds \\\"was\\\", its old label.\"\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: a preformatted block, such as the command or SQL to approve.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: label of the disclosure that reveals --detail, such as Show the SQL; up to 40 characters.\"\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui ask: suggested answers as a JSON array of strings.\"\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui info: one button as a JSON object {label, url}.\"\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"description\": \"notify: up to 6 related files, pages or approvals as a JSON array of {label, path}, {label, url} or {label, approval}; any ui.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"description\": \"Text that workspace write writes instead of stdin.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"description\": \"Start an Agent Farm profile (such as greenfield/implementer:opus) in a chat tab.\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"description\": \"A launch argument for --agent-farm, shown to the agent in its LAUNCH CONTEXT; repeatable.\"\n },\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"description\": \"autopilot stack push only: the waiting item (an id from `runpane autopilot waiting`) to put on the stack.\"\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"description\": \"notify only: relay into this waiting item (an id from `runpane inbox`): the same card gains the choices, context and links, and the call waits on it.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"description\": \"notify only, for Autopilot's own items: the Session the item is about.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options, instead of --choices: 2 to 4 questions as a JSON array of {title, choices}, each with 1 to 9 choices and one pick.\"\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: say how you took the person's reply to this approval; answer or question follows the id.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"description\": \"notify --took: the word after the id, as a flag (the MCP tool's tookAs).\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"description\": \"notify --took <id> question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: save a new version of this approval after the person's question, and wait on it.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: what changed, one line per change.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"description\": \"inbox ack only: record this D-n on each named item.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"description\": \"autopilot answer only: answer with this choice's label.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"description\": \"autopilot answer only: why, recorded on the item: a D-n, a document or the person's words.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--split\",\n \"description\": \"Open the tab in split view beside the primary tab group (default for panels open).\"\n },\n {\n \"name\": \"--tab\",\n \"description\": \"Open the tab in the current tab group instead of split view. On autopilot stack push it takes a value instead: --tab <path-or-url> adds a tab (a file, or an http(s) URL); repeatable.\"\n },\n {\n \"name\": \"--pinned\",\n \"description\": \"Request pinning at creation; Session association still clears the first pin. Outside a Session, creation pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"description\": \"Create the pane unpinned instead of the default pinned (the UI's favorite/pin star).\"\n },\n {\n \"name\": \"--no-associate\",\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session.\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn, or a tab holds unsent input; for panels close, close the tab even if an agent opened it, its agent is busy, or it holds unsent input (a stuck tab); for lock release, release another owner's lock.\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"description\": \"Also check and remove an adopted Pane's worktree when archiving it. The local branch is kept.\"\n },\n {\n \"name\": \"--merged\",\n \"description\": \"With panes archive --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work. Other Panes are skipped with a reason (pr-open, pr-not-merged, pr-status-unknown, or a local safety code).\"\n },\n {\n \"name\": \"--closed\",\n \"description\": \"With panes archive --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n {\n \"name\": \"--launch\",\n \"description\": \"Run an adopted pane's agent resume command immediately instead of staging it.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--follow\",\n \"description\": \"Continue waiting for workspace events until interrupted.\"\n },\n {\n \"name\": \"--until-event\",\n \"description\": \"Watch only: wait across daemon calls for the first matching event, print it, and exit 0. --timeout-ms becomes the total deadline (exit 124). For harnesses that wake an agent when a process exits.\"\n },\n {\n \"name\": \"--ack-now\",\n \"description\": \"Advance a named cursor immediately for at-most-once delivery.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"description\": \"Include up to 120 characters of unsubmitted composer text in ready events.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"description\": \"Resume every stopped agent panel in every non-archived Pane.\"\n },\n {\n \"name\": \"--include-shells\",\n \"description\": \"Include ordinary shell panels in follow mode.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"description\": \"Disable redacted held-input detection under --follow (STUCK lines, and heldInputPresent in JSON).\"\n },\n {\n \"name\": \"--self-test\",\n \"description\": \"Probe the current daemon watch path without advancing a named cursor.\"\n },\n {\n \"name\": \"--report\",\n \"description\": \"Prepare a redacted doctor report; external filing still requires --yes.\"\n },\n {\n \"name\": \"--fix\",\n \"description\": \"Update each npm-installed runpane older than the Pane-managed CLI to its version. Never removes a file.\"\n },\n {\n \"name\": \"--read-only\",\n \"description\": \"Serve only read-only MCP tools.\"\n },\n {\n \"name\": \"--template\",\n \"description\": \"Print the handoff note template, with this checkout's (or Session's) origin and git state filled in.\"\n },\n {\n \"name\": \"--push\",\n \"description\": \"Before a handoff, commit uncommitted work as WIP and push the branch (never forces).\"\n },\n {\n \"name\": \"--note-only\",\n \"description\": \"Hand off to a fresh agent with the note, instead of carrying and resuming your own session.\"\n },\n {\n \"name\": \"--interrupt\",\n \"description\": \"Stop the current agent turn, wait for an empty idle composer, then submit and verify the replacement.\"\n },\n {\n \"name\": \"--expect-report\",\n \"description\": \"Pane expects a `runpane report` and emits agent.report.missing if the agent goes ready or exits without one. The default for work an agent sends; use it to ask for one on work marked --source user.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"description\": \"Send work without waiting for a `runpane report` (fire-and-forget input); no agent.report.missing follows.\"\n },\n {\n \"name\": \"--all\",\n \"description\": \"inbox ack only: clear every item of the --session.\"\n },\n {\n \"name\": \"--paused\",\n \"description\": \"loops create only: create the loop paused.\"\n },\n {\n \"name\": \"--chat\",\n \"description\": \"Open the agent as a chat tab (Claude or Codex over ACP, no terminal) instead of a terminal tab.\"\n },\n {\n \"name\": \"--next\",\n \"description\": \"autopilot stack push only: put the entry right after the one on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--approve\",\n \"description\": \"autopilot answer only: approve the item.\"\n },\n {\n \"name\": \"--deny\",\n \"description\": \"autopilot answer only: deny the item.\"\n },\n {\n \"name\": \"--hook\",\n \"description\": \"runpane panels conversation: read a Claude Code SessionStart hook's JSON from stdin and report it for PANE_PANEL_ID. Prints nothing and always exits 0.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane sessions list [--json] [--pane-dir <path>]\",\n \" runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions pause|resume --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \" runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane loops list [--json]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read|write|exec|<command>\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \" --report Prepare a redacted, inspectable report\",\n \" --body-file <path|-> Read report evidence from a file or stdin\",\n \" --title <text> Report title\",\n \" --yes Confirm creating one greenfield-inc/Pane issue\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Remote daemon data directory (default: ~/.pane_remote)\",\n \" --pane-path <path> Existing Pane executable used to perform repair\",\n \" --yes Skip the restart confirmation\",\n \" --json Print one machine-readable repair result\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Repository display name\",\n \" --build-script <script> Build (setup) script; runs in each new worktree\",\n \" --run-script <script> Run script; runs on the Play button\",\n \" --archive-script <script> Archive script; runs before a worktree is removed\",\n \" --worktree-folder <path> Folder for new worktrees, absolute or relative to the repository\",\n \" --open-ide-command <command> Command the Open in IDE button runs\",\n \" --system-prompt <text> Project system prompt\",\n \" --env <json> Project variables to add or replace, a JSON array: [{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://...\\\"}, {\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]\",\n \" --env-remove <name,...> Project variables to remove\",\n \" --clear <setting,...> Clear settings, e.g. run-script,archive-script; cleared scripts fall back to the repo's config file\",\n \" --from-json <path|-> Settings object: {\\\"runScript\\\": \\\"pnpm dev\\\", \\\"archiveScript\\\": null, \\\"envVars\\\": {\\\"set\\\": [...], \\\"remove\\\": [\\\"OLD\\\"]}, ...}; flags override it\",\n \" --dry-run Show the result without saving\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Limit results to one saved repository\",\n \" --pane <pane-id> Return one Pane, including an idle Pane with zero usage\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read <path>\",\n \" runpane workspace <machine> write <path> (content from --content or stdin)\",\n \" runpane workspace <machine> exec -- <command>\",\n \" runpane workspace <machine> <runpane command> (any command that talks to Pane, e.g. sessions list --json)\",\n \" runpane workspace enable|disable\",\n \"\",\n \"<machine> is a Tailscale name, unique prefix, or IP. Your own machines are reachable, and a teammate's machine when its owner set Who can connect to Everyone on tailnet.\",\n \"Without <machine>, read, write, and exec route a path that cannot exist here (C:\\\\... on a Mac) to the one joined machine it fits.\",\n \"Commands that only run here (doctor, agent-context, mcp, docs) run there with: runpane workspace <machine> exec -- 'runpane doctor'\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes, a busy agent, or unsent input\",\n \" --remove-worktree Also check and remove an adopted Pane's worktree (the branch is kept)\",\n \" --session <id|name> Archive the Panes associated with this Session (requires --merged)\",\n \" --merged Only archive Panes that are clean and pushed, or merged via a PR\",\n \" --closed With --session --merged, also archive Panes whose PR closed unmerged\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Refresh and print archive safety evidence without archiving\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to pin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without pinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to unpin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without unpinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to rename\",\n \" --name <new-name> New non-empty Pane/session name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without renaming the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to focus\",\n \" --panel <panel-id> Optional panel/tab id inside the Pane to select\",\n \" --source <user|agent> Mutation source; does not change focus behavior\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Accepted and ignored; agent-context never contacts Pane\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \"\",\n \"Runs the Pane MCP server over stdio. Each runpane command with a JSON result becomes a tool that returns the same JSON as `runpane <command> --json`. Commands that need --yes take `yes: true` instead.\",\n \"Pane registers this server with Claude Code, Codex, and Cursor automatically (Settings > AI & Agents). To register it by hand:\",\n \" claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \" codex mcp add pane -- npx --yes runpane@latest mcp\",\n \"\",\n \"The MCP server ships in the npm package and inside the Pane app. The Python package does not include it.\",\n \"\",\n \"Options:\",\n \" --toolsets <name,...> core (default), all, read, repos, panes, panels, agents, sessions, workspace, git, docs, links, admin\",\n \" --read-only Serve only read-only tools\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tab to close\",\n \" --dry-run Report whether the tab would close, and why not, without closing it\",\n \" --force Close it even if an agent didn't open it, its agent is busy, or it holds unsent input\",\n \" --source <user|agent> Mark mutation source\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"\",\n \"Options:\",\n \" --query <text> Words to search for.\",\n \" --limit <count> Maximum results; defaults to 5.\",\n \" --json Print machine-readable output.\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"Read one Pane doc, help topic, or skill in full.\",\n \"\",\n \"Options:\",\n \" --doc <path> Path from docs search, such as docs/PANE_MCP.md.\",\n \" --json Print machine-readable output.\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --name <name> Name for the new Pane.\",\n \" --agent <codex|claude|cursor> Built-in agent to launch.\",\n \" --tool-command <command> Custom terminal command instead of a built-in agent.\",\n \" --prompt <task> The task to send once the agent is ready.\",\n \" --base-branch <branch> Branch to start the worktree from.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --limit <count> Screen lines to return; defaults to 40.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --text <message> The message to submit.\",\n \" --as-file-pointer Write the message to a prompt file and send `Read and follow <path>`.\",\n \" --no-expect-report Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" runpane loops list [--json] [--pane-dir <path>]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form; ~ is that machine's home.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form.\",\n \" --content <text> Text that replaces the file. Without it, stdin is written.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" --cwd <path> Working directory on that machine, in any path form; defaults to its home.\",\n \" --timeout-ms <milliseconds> Stop the command after this long (default 120000, at most 540000).\",\n \" --json Print machine-readable output.\",\n \" -- <command> The command line, run by that machine's shell.\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"Hand your task to another agent. You write the note (runpane handoff --template); the CLI checks it,\",\n \"makes sure your branch is pushed, sends the note to the destination, and starts the agent there.\",\n \"From a Pane tab, a handoff to the same agent (Claude, Codex, Cursor or OpenCode) carries your own session:\",\n \"another machine resumes a copy of it, this machine resumes a fork. The CLI then checks the receiver\",\n \"continued it (continued, inconclusive, or not-continued, which exits 1 and keeps the receiver).\",\n \"--note-only, another agent, or no session to carry starts a fresh agent with the note instead.\",\n \"From a Session's terminal it skips git and creates the Session on the other machine; a same-agent\",\n \"Session resumes your conversation when first opened, otherwise it reads the note. The sender Session stays.\",\n \"\",\n \"Options:\",\n \" <destination> Freeform: agent, model, effort, and machine, such as \\\"codex gpt-5 high on parsas-macbook-pro\\\".\",\n \" --template Print the note template, with this checkout's (or Session's) origin and git state filled in.\",\n \" --note-file <path|-> The note to check and send; every template section must be filled in.\",\n \" --machine <name> Destination machine (Tailscale name or unique prefix); overrides the text.\",\n \" --agent <codex|claude|cursor> Receiving agent; overrides the text.\",\n \" --model <model> Model for the receiving agent; overrides the text.\",\n \" --effort <level> minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\",\n \" --repo <selector> Saved repository on the destination; defaults to the one with the same remote.\",\n \" --push Commit uncommitted work (not the note) as WIP and push the branch first (never forces).\",\n \" --note-only Start a fresh agent with the note instead of carrying your own session.\",\n \" --dry-run Check destination reachability, saved repo and OS; print note and git state; send nothing. From a Session: the name it would get.\",\n \" --json Print machine-readable output.\",\n \"\",\n \"Examples:\",\n \" runpane handoff --template > ~/handoff.md\",\n \" runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \" runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" python -m runpane loops list [--json]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" python -m runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \" --report\",\n \" --body-file <path|->\",\n \" --title <text>\",\n \" --yes\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --yes\",\n \" --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --name <name>\",\n \" --build-script <script>\",\n \" --run-script <script>\",\n \" --archive-script <script>\",\n \" --worktree-folder <path>\",\n \" --open-ide-command <command>\",\n \" --system-prompt <text>\",\n \" --env <json>\",\n \" --env-remove <name,...>\",\n \" --clear <setting,...>\",\n \" --from-json <path|->\",\n \" --dry-run\",\n \" --yes\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" python -m runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" python -m runpane secrets list [--project <project>] [--json]\",\n \" python -m runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" python -m runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | python -m runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" python -m runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \"\",\n \"runpane workspace list|read|write|exec|enable|disable ship in the npm package: npx --yes runpane@latest workspace list\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" python -m runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --remove-worktree\",\n \" --session <id|name>\",\n \" --merged\",\n \" --closed\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --name <new-name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --panel <panel-id>\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \" --pane-dir <path> Accepted and ignored\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp\",\n \"\",\n \"The Pane MCP server ships in the npm package and inside the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest mcp\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --dry-run\",\n \" --force\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"python -m runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"python -m runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"python -m runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"python -m runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"python -m runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"python -m runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"python -m runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"python -m runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"`runpane docs search` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs search\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"`runpane docs read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs read\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents start` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents start\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents status` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents status\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents send` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents send\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" python -m runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"python -m runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"python -m runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"python -m runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" python -m runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" python -m runpane loops list [--json] [--pane-dir <path>]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"python -m runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace list` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace list\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace enable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace enable\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace disable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace disable\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"`runpane workspace read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace read\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"`runpane workspace write` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace write\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"`runpane workspace exec` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace exec\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"`runpane handoff` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest handoff\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"python -m runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"python -m runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane mcp\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane repos get --repo Pane --json\",\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --worktree-folder ../pane-worktrees --yes --json\",\n \"runpane repos update --repo Pane --clear run-script --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://api.test\\\"},{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\",\n \"runpane repos rename --repo 3 --name Lev --yes --json\",\n \"runpane secrets list --json\",\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes\",\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\",\n \"runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\",\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook tests against Stripe test mode.\\\" --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes cost --pane <pane-id> --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock release --name testing-account --json\",\n \"runpane lock list --json\",\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops list --json\",\n \"runpane loops run --loop <loop-id> --yes --json\",\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\",\n \"runpane agents status --pane <pane-id> --json\",\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane panels last-message --panel <panel-id> --json\",\n \"runpane panels conversation --conversation <id> --source resume --json\",\n \"runpane panels close --panel <panel-id> --yes --json\",\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\",\n \"runpane panes pause --pane <pane-id> --yes --json\",\n \"runpane panes resume --pane <pane-id> --yes --json\",\n \"runpane docs search --query \\\"archive a pane\\\" --json\",\n \"runpane links create --pane <pane-id> --json\",\n \"runpane panes git-status --pane <pane-id> --json\",\n \"runpane help\",\n \"runpane <command> --help\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. The remote-host wizard asks only for a name, then runs interactive Tailscale setup with automatic port selection; explicit install daemon flags remain available for SSH and manual URLs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` defaults to both app and active supported CLI, with independent supervision, staging, durable status, and explicit restoration outcomes. Prerequisites, status fields/exit codes, draft handling and manual recovery are documented in [Update recovery](UPDATE_RECOVERY.md).\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, every `runpane` on `PATH` against the Pane-managed CLI, and remote-daemon hints. `--fix` updates npm installs of `runpane` older than the Pane-managed CLI to its version and never removes a file. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane daemon repair` rewrites and restarts only the managed remote-daemon launcher/service. It never creates pairing credentials, changes tunnels, or downloads Pane; doctor only recommends it and never runs it automatically.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output. An unknown command exits 2 and lists the closest command names; with `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout.\",\n \"`runpane mcp` runs a stdio MCP server whose tools are generated from this contract: every command with result `jsonSchemas` becomes a tool that runs `runpane <command> --json` and returns its output. Only the npm package and the Pane app include it; the Python wrapper prints how to run it with Node and exits non-zero.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane repos get` prints a saved repository's Project Settings. Each script shows its saved value, the fallback from the repository's config file, and the one Pane will run.\",\n \"`runpane repos update` changes a saved repository's Project Settings through the same save as the Project Settings dialog. It requires `--yes`, supports `--dry-run`, clears settings with `--clear`, and adds, replaces or removes project environment variables with `--env` and `--env-remove`.\",\n \"`runpane repos rename` trims and sets a saved repository's display name, the same rename as the repository settings in the app, and returns the updated repository record.\",\n \"`runpane secrets` manages Pane secrets: named values, addressed as `<project>/<NAME>`, that every machine saved in Pane keeps a copy of. `list` shows names, times and machines only. `set` reads the value from stdin. `delete` removes one everywhere. `exec` runs a command with secrets in its environment and returns its exit code. `request` asks the person on a masked Notify card and returns only `saved` or `declined`. No command prints a value, and the MCP server has no tool that sets or reads one. These commands run on the machine itself, so `runpane workspace <machine> secrets ...` is refused; use `runpane workspace <machine> exec -- runpane secrets ...`.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes cost` reports estimated token costs per Pane for the last 30 days, including per-model breakdowns and cache efficiency; unscoped output includes an Unattributed bucket that reconciles against workspace totals.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed. New Panes are pinned into the UI's favorite/pin set by default, except when the CLI runs inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID`), where child worktrees default to unpinned. Explicit `--pinned` / `--no-pinned` override creation defaults. First Session association clears an existing pin, including an explicit creation pin; use `panes pin` after association to pin a child. This avoids duplicating Session children in the global pinned list. Manual pins applied afterward are preserved. Panes created interactively in the Pane UI are unaffected. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"For `panes create --wait-ready` (and `panels create`, `panes adopt --launch`), ready means an identified agent at its own input box: declared with `--agent`, or detected from the command, its foreground process or its screen. A `--tool-command` that never shows a supported agent is not ready: `readiness.ok` is false with `readiness.reason.code` `no-agent-detected` after the timeout, or `command-exited` as soon as the command exits back to the shell, and nothing is typed into it. `agent-not-ready` means the agent was detected but its input box was not ready in time. CLI readiness is confirmed on consecutive polls. Codex also requires a recognized configured model or context footer below its live composer; its provisional startup composer is not ready. Hidden or unrecognized Codex status lines conservatively time out rather than prove readiness. Startup prompts return blocked with the prompt's own text in blocked.message and a suggestedCommand, and Pane never answers them, apart from Claude's folder trust for a Session's own folder: first-run trust and daemon dialogs and Agent Farm's `Trust this repository workspace? [y/N]` as blocked.kind=first-run-dialog; Codex's security setup banner and any other prompt, including a wrapper's or shell's `[y/N]` before the agent appears, as agent-prompt. A starting message blocked this way is held and sent once the prompt is answered and the agent's input box is ready. Every create with a starting message returns `initialInput`. Pane stages the text in the composer, presses the agent's submit key separately (Ctrl+Enter for Codex, declared or detected), and checks where it went: `initialInput.delivery` is `taken` or `queued` (from the agent's transcript or its screen), `in-composer`, or `unknown` (`argv` means a prompt was routed at launch; Pane gives the transcript up to 10 seconds before settling on unknown). `initialInput.verifiedSubmitted` is true exactly when it is `taken` or `queued`, and the create's `ok` is false unless a Claude or Codex starting message is verified. Cursor and OpenCode have no acceptance probe: their delivery stays `unknown` with a delivery.message saying so, `verification: unverifiable`, and `ok` follows readiness. Without `--wait-ready` the create returns at once with `initialInput.verifiedSubmitted: false`, a delivery.message saying the call did not wait, and a `panels wait` nextCommand; Pane still sends the message once the agent is identified and at its input box, and gives up without typing it if no supported agent appears within 30 seconds.\",\n \"`runpane panes archive` refreshes the configured upstream, reports exact unpushed commit evidence, and refuses unsafe archive operations unless `--force` is used. A branch whose upstream is gone counts as pushed when a merged GitHub pull request has HEAD as its head (`safetyCheck.mergedViaPr`). Add `--dry-run` to inspect the same evidence without archiving. `--remove-worktree` applies the same check and removal to an adopted worktree; local branches are always kept. Successful archives report `worktreeCleanup: completed` once the worktree is gone from its path and from git; `trashDeletion: pending` means its files are still being deleted in the background. `runpane panes archive --session <id|name> --merged` archives every Session Pane that is clean and pushed or merged, and reports a reason for each skipped Pane. Archive refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless `--force` is used, and the Session sweep skips it. Add `--closed` to the sweep to also archive Panes whose PR closed without merging.\",\n \"`runpane panes rename` trims and updates a Pane's display name without changing its worktree, branch, panels, or focus, and returns the updated pane summary.\",\n \"`runpane panes focus` raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Because it steals the user's window focus, run it only on an explicit user request to open, focus, show, or switch to a Pane; never focus a Pane proactively, the same doctrine that keeps `panes create` background/no-focus for `--source agent`.\",\n \"`runpane panels open` opens a URL or a file from the Pane worktree as a tab in an existing Pane (default: the calling panel's Pane from `PANE_SESSION_ID`), in split view beside the agent unless `--tab` is passed. Files open in an editor tab with read-only previews for supported formats, and HTML opens as editable source; an existing tab showing the same target is reused. It brings the tab forward for clients already showing that Pane, and never raises or focuses the Pane window or switches anyone's Pane.\",\n \"`runpane panels close --panel <panel-id>` closes a tab exactly like closing it in Pane (a terminal tab's process stops) and never archives the Pane or touches its worktree. Only a tab an agent opened with `--source agent` can close; Pane records that opener, and `panels list` shows it as `openedBy`. It refuses, without closing, the person's tabs and every tab from before Pane recorded openers (`opened-by-user`), permanent tabs (`permanent`), an agent mid-turn or waiting for an answer (`agent-busy`), unsent composer text or unsaved editor edits (`unsent-input`), and a live tab whose input Pane cannot read, such as agents other than Claude and Codex or chat tabs (`input-unknown`). A refusal prints `{ ok: false, closed: false, refused: { code, message, next } }` and exits 1. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports `wouldClose` and any refusal without closing. `--force` closes the tab anyway, as its close button does, for a stuck tab or one the person asked to close (`forced: true`); only a permanent tab still stays.\",\n \"`runpane panels list` lists tool panels inside one Pane session. Each panel with a saved agent session ID reports it as `resumeId`, with `resumeSource` (`agent-farm`, `configured`, or `agent`) naming the rule that reopens the conversation.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"`runpane panes create --branch <name>` creates the Pane's worktree on exactly that new branch, slashes included (for example `agents/w5a`). The name is checked with `git check-ref-format --branch`, creation fails if the branch already exists, and Pane never renames it to make it unique. `--worktree-name` still names the directory and defaults to `--name`. `--base` is an alias for `--base-branch`, and `--prompt-file` for `--initial-input-file`. `panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`. When the base is a remote-tracking branch (the default `origin/main`, or one named with `--base`), Pane fetches it first, waiting at most about 10 seconds, so the Pane starts at the remote's current tip. Each created item reports `base` (`ref`, `commit`, `refresh`); if the fetch fails the Pane still starts, from the local ref, and `base.notice` says so and how to catch up.\",\n \"`runpane panes adopt --launch` accepts `--prompt`, `--prompt-file`, `--wait-ready`, and `--ready-timeout-ms`, and reports `readiness`, `initialInput`, and `nextCommand` like `panes create`. A prompt without `--launch` is an error rather than being dropped.\",\n \"If composer submission cannot be verified without risking a duplicate, the create item is unsuccessful with `initialInput.staged`, `initialInput.attempts`, `initialInput.blocked.kind: submission_unverified`, and an actionable `nextCommand`. The CLI-facing `--prompt` alias maps to this canonical `initialInput` result.\",\n \"When running from WSL while Pane is installed on Windows, a Linux-installed runpane cannot reach Pane: it looks for a missing `/tmp/pane-daemon.../daemon.sock`. Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane` from any WSL shell) runs the Windows CLI through WSL interop and converts path arguments. With a Pane build older than that shim, invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"`runpane watch` waits for agent, Pane, panel, and PR events from the daemon journal without polling. `--follow` keeps watching with defaults tuned for an agent watching unattended (quiet, batched, and it ends itself after 3 hours with nothing happening); individual duration flags override these defaults. Cadence requires a named follow consumer; `--follow --since <generation>` has no consumer name and bypasses settling and batching. `--until-event` waits for one event worth waking for, prints it immediately without follow settling or batching, and exits 0. The defaults are listed in the contract under defaults.watch.\",\n \"A watch releases its pending slot when its client socket or named pipe disconnects. Re-arming the same named cursor takes over the previous request; the superseded watcher exits with a WATCH ERROR instead of competing for the cursor. Anonymous concurrent watchers retain the eight-wait limit. Each request also has a 125-second lease (slightly longer than the maximum 120-second long poll), so an abandoned request is reclaimed even if no disconnect arrives.\",\n \"The desktop app treats one consumer name specially: while a `runpane watch --as dictate` request is in flight, and for 30 seconds after the last one ends, Pane skips its own \\\"needs your input\\\" and \\\"finished\\\" desktop notifications, because [Dictate](https://github.com/parsakhaz/open-dictate) shows those events on its pill. When that watcher stops, Pane's notifications come back. The CLI's flags and output are unchanged.\",\n \"`sessions list` list durable named orchestration Sessions.\",\n \"`sessions create` create a durable named orchestration Session and its hidden terminal owner.\",\n \"`sessions get` read one durable named orchestration Session.\",\n \"`sessions update` update a Session overview from structured JSON.\",\n \"`sessions pin` and `sessions unpin` set the Session pin star through the existing UI update path. Create JSON accepts `isPinned` (default false); update JSON preserves the pin when the field is omitted.\",\n \"`sessions set-agent` switch the durable terminal agent for a named Session.\",\n \"`sessions associate` associate a user-visible Pane with a named Session.\",\n \"`sessions detach` detach a Pane from a named Session.\",\n \"`sessions overview` read a live status, activity, git, and pull request overview for a named Session. Each panel in `panes[].panels[]` carries its own latest `runpane report` as `report`, so one call shows what every worker last reported and when (`reportedAt`, next to the panel's `state` and `lastActivityAt`). When a report's summary is a complete JSON object, such as a status file sent with `--summary-file`, `report.data` holds it exactly as the worker gave it; Pane never reads, renames or validates its fields. A summary that is not a JSON object (plain text, an array, a scalar or invalid JSON), is nested more than 64 levels deep, or was truncated (`summaryTruncated`), has no `data`. A panel that never reported has no `report`. `panes[].report` is still the newest report among the Pane's panels. Each panel also has `type` (`terminal`, `browser`, `editor`, `explorer`, `diff`, ...): a Pane linked whole lists every tab, with `explorer` and `diff` last, and a link that names tabs lists exactly those.\",\n \"`runpane lock acquire|release|list` coordinate a resource shared between agents, such as one test account. The caller's Pane and panel own the lock; it is scoped to the owner's Session (or global outside one), renews for the same owner, and is released on TTL expiry, owner panel exit, or owner Pane archive. `--wait` blocks in the daemon until the lock comes free.\",\n \"`runpane loops list|create|update|run|pause|resume|delete` manage loops (scheduled tasks): a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, agents use their harness's own sleep and wake. See [LOOPS.md](LOOPS.md).\",\n \"`runpane agents start|status|send` finish the three common agent jobs in one call each: start an agent on a task (a new tab in the work's Pane with `--pane <id>`, or a new Pane for new work), check on it, and send it a follow-up.\",\n \"`runpane report --state ready|blocked|failed|done` is how a worker hands back its result. It stores the latest report on the worker's panel (state, `--pr`, `--head`, up to 16,000 characters of `--summary` or `--summary-file`, and the `--question` a blocked worker needs answered), journals an opt-in `agent.report` watch event (`REPORT <pane-name> pane <pane-id> panel <panel-id> ready pr#747 fc5dce9`; it skips the `--min-interval` batch), records it as Session activity, and shows it in `agents status`, `panels list`, and `sessions overview` (each panel's own report as `panes[].panels[].report`, with a JSON object summary as `data`, and the Pane's newest as `panes[].report`). Inside a Pane terminal the panel comes from `PANE_SESSION_ID` and `PANE_PANEL_ID`; elsewhere pass `--pane` and `--panel`. It prints how many watches were waiting for the report and received it (`Delivered to 1 waiting watcher.`, `liveWatchers` in JSON). Zero means nobody was waiting right now, not that the report is lost: it stays on the panel, and a named-cursor watch receives it when it next waits.\",\n \"`runpane notify` asks the person a question, or tells them something, on screen wherever they are, and prints their answer: `--ui options` with 1 to 9 `--choices` returns `{answer, note?}` (the chosen label), or with 2 to 4 `--questions` (`[{title, choices}]`) one pick each, as `{answer, answers}`, `--ui approve` returns `{approved, note?}`, `--ui ask` returns `{answer}`, and `--ui info` returns `{delivered: \\\"screen\\\"|\\\"inbox\\\"}` at once. Any ui takes up to 6 `--links` (`{label, path}`, `{label, url}` or `{label, approval}` for another approval), shown as chips on the card. When the person types a reply it returns `{reply, picks?, id, next}` (or, with no open call, types a `reply:` line into the asking tab): the reply may answer the question or ask one. `runpane notify --took <id|Q-n> answer` records it as the answer; `--took <id|Q-n> question [--status <text>]` shows that the agent is getting more context, and `runpane notify --update <id|Q-n> --asked <text> --what-changed <text>` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` saves a new version and waits on it. `--took` prints `{took, id, next?}`. Only the reply's reader (the asker, the orchestrator that relayed it, or Autopilot) may run them. The call waits with no time limit. A `{waiting: true, id, next}` result means the wait should continue with `runpane notify --resume <id>`; `runpane mcp` asks for it only for clients whose tool calls time out. When Pane is not running or Notify is off it prints `{unavailable, next}` and exits 0, so the agent asks in its terminal. Invalid input fails before anything is shown, with a message naming the field. A Session orchestrator adds `--relays <item>` (an id from `runpane inbox`) to relay a worker's question: the same card gains the choices, context and links, the call waits on it as usual, and the answer also goes straight to the worker's tab. Autopilot's own item takes `--about-session <id|name>`, the Session it is about. It needs no `--yes`: asking the person is the point.\",\n \"Work sent to an agent through `runpane panels create`, `panes create` (with a prompt), or `panels submit` records that the sender waits for a `runpane report`, unless the request says `--source user` (Pane's own UI does) or `--no-expect-report`; `--expect-report` asks for one anyway. Typing into a tab never does. If the agent then goes ready or its panel exits without a report, Pane journals one opt-in `agent.report.missing` entry (`MISSING REPORT <pane-name> pane <pane-id> panel <panel-id> ready|exited`) whose `lastOutput` is the agent's last transcript reply or last screen lines; it skips the `--min-interval` batch and reaches only watchers that list it in `--kinds`. While the agent's screen shows background work (Claude's `· 1 shell` footer or a running background subagent row, Codex's `background terminal running` line), Pane waits and looks again every 10 seconds instead. `agent.blocked` entries carry `screenLines`, the prompt the agent waits on, and are journalled once a panel has stayed blocked for 2 seconds.\",\n \"The orchestrator guide prescribes submit without `--wait`, followed by its named watch; use one completion consumer. As an optional CLI route, `--wait` on `runpane panels submit`, `panels create`, and `panes create` turns the expected report on and blocks on that panel until the first `agent.report`, `agent.report.missing`, `agent.blocked`, `panel.exited`, or `pane.gone`, then prints one line of JSON (`dispatchWaitResult`): the outcome, ids, the report's state, summary, `summaryPath`, PR and head, `screenLines` for a prompt, or `lastOutput` for a missing report, never the transcript. It exits 0 for a report, 3 missing, 4 blocked, 5 exited or gone, and 124 when `--timeout <duration>` passes. It follows the journal from the position the daemon took before delivering the work, so nothing that happens right after delivery is missed. It reuses watch --until-event and returns indeterminate (exit 6) if the journal epoch changes or retained history is lost; inspect the panel and inbox before retrying. Report-expected dispatches are serialized per panel and refused before writing while a result or missing-output collection is outstanding. A send with --no-expect-report is a reply (an answer, or an addition to the current work): it is delivered while a report is outstanding and leaves that expectation in place. Then watch for the original result.\",\n \"`runpane inbox [--session <id|name>] [--json]` lists what a Session's orchestrator has not acknowledged: reports, missing reports, and on-screen questions (a question clears itself once its panel moves on), plus `answers`, what its workers were told, kept 7 days; an answer with no `decision` still needs its D-n. `runpane inbox ack <item-id>...` or `--all --session <id|name>` clears items, and `runpane inbox ack <item-id>... --decision <D-n>` records the D-n, even on an item already cleared. An orchestrator runs it when it starts or resumes, to catch anything that finished while it was down; Pane never injects the inbox into its turns.\",\n \"`runpane panels resume --panel <panel-id>` restarts a stopped terminal panel in place, keeping its panel id: an agent panel relaunches with its resume command (Claude `--resume <session>`, `codex resume <session>`, Agent Farm `agent-farm run <profile> --resume <id>` with the ID Agent Farm printed; the result's `agentSessionId` names it), the same restart the app runs when it shows the panel, without taking focus. A running panel is left alone (`action: \\\"already-running\\\"`); one whose agent exited back to its shell is stopped, and resume runs its saved command again in that shell (`action: \\\"resumed\\\"`). If that command exits at once, `--wait-ready` returns `ok: false` with `reason.code: \\\"command-exited\\\"` and the error line it printed. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope and skips paused ones, `--concurrency` at a time (default 3), then, with `--wait-ready`, waits on the resumed panels together, and returns `{ resumed, alreadyRunning, failed, notReady, items[] }` with each panel's own result, readiness, and blocker or error. After a Pane restart, or once an agent exits back to its shell, stopped panels report `running: false` (`initialized` stays true while the shell lives) in `panels list`, `panels screen`, and `panels wait` (which returns `stopped: true` instead of timing out), and `nextCommand` points at `panels resume`. `runpane watch` reports them as `panel.stopped` (`STOPPED ...`) when `--kinds` lists that kind; other consumers and `workspace state` see `agent.unknown` with `running: false`, never `agent.ready`.\",\n \"`runpane panels pause --panel <panel-id>`, `panes pause --pane <pane-id>` and `sessions pause --session <id|name>` pause a tab, a Pane, or a Session's chat and Panes: Pane stops each terminal tab the way quitting does, without asking, keeps its agent's conversation, and marks it paused. A chat tab can't pause yet: `panels pause --panel` on one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE`, and `panes pause` and `sessions pause` leave its agent running and count it in `chatTabsNotPaused`. A paused tab stays stopped through clicks, app restarts and updates until `panels resume --panel`, `panes resume` or `sessions resume` restarts it in its saved conversation. Each returns `{ action, scope, panelIds, unchanged, failed, chatTabsNotPaused, message, nextCommand }`. `panes list` reports a Pane's `paused` mark (`{ at, by: \\\"manual\\\" | \\\"auto\\\", idleHours? }`) and `inactiveSince`, the latest terminal output or input in its tabs or the last time the person selected it; `panels list` reports each tab's `paused`. `runpane watch` reports `panel.paused` (`PAUSED ...`) and `panel.unpaused` (`UNPAUSED ...`) under `--follow`, to a Session-scoped watch, or when `--kinds` lists them.\",\n \"`runpane panels last-message --panel <panel-id>` reads a Claude or Codex agent's last reply from its transcript (up to `--limit` characters, default 20,000, keeping the end and reporting `truncated`). It never scrapes the screen: without a transcript it prints `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and exits 1.\",\n \"`runpane panels conversation --conversation <id> [--source startup|resume|clear|compact]` records which conversation a terminal panel's agent is on (daemon action `runpane:panels:conversation`, request `{ panelId, paneId?, conversationId, source? }`, result `{ ok: true, paneId, panelId, conversationId, source?, sure }`; `sure` is true when the report came since the panel's agent started). It is not an `agent.report` event. The panel comes from `--panel` (with an optional `--pane`), else `PANE_PANEL_ID` and `PANE_SESSION_ID`, as for `runpane report`. `runpane panels conversation --hook` is the command for a Claude Code `SessionStart` hook: it reads the hook's JSON from stdin (`session_id`, `source`; at most 64 KiB, for at most 2 seconds), reports it for `PANE_PANEL_ID`, prints nothing on stdout or stderr, and always exits 0: outside Pane, on bad input, when Pane is not running, and with a Pane that predates the action. Without `--hook` it prints `Recorded conversation <id> for panel <panel-id>.` and fails like any other command.\",\n \"Commands with a contract `daemonAction` (the `panes` git, script, restore, and move commands, `folders list|create`, and `links open`) call the same Pane daemon channel as the matching button in the app and print `{ ok, data, error }`. Destructive ones add a pane:// `link` to review the Pane.\",\n \"`runpane links create` builds `pane://open?...` links; opening one in Pane selects what it names and never changes Pane state.\",\n \"`runpane docs search|read` search and read Pane docs, help, and installed Pane Chat skills offline. They ship in the npm package and the Pane app only.\",\n \"`runpane handoff` hands a task to another agent on this or another of your machines. The sending agent writes the note from `runpane handoff --template` (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state); `runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md` checks every section is filled in, requires the branch to be pushed (or pushes it with `--push`, never forcing), writes the note to that machine's `~/.pane/handoffs/` and starts the agent in a new Pane branched from the sender's branch. When the sender is a Pane tab running a Claude, Codex, Cursor or OpenCode session and the receiver is the same agent, the handoff carries that session: on another machine its files are copied into that machine's agent store and the receiver resumes the same id; on this machine the receiver resumes a fork, because two agents never resume one session on one machine. The receiver starts with no prompt, and the CLI runs the continuity check and reports JSON `continuity.result` `continued`, `inconclusive` (exit 0; `next` gives a probe question) or `not-continued` (exit 1; the receiver is kept and `next` gives the `--note-only` command). `--note-only`, a different agent, or a tab with no carried session starts a fresh agent with the note (`fresh-agent`). JSON `continuity` is `{ carried, result, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }`: `sessionId` is the sender's session, `resumedId` the id the receiver resumes (the fork's on this machine), `checks` are the continuity checks (`historyLoaded` is null when there was nothing to compare; `originalUntouched` is set for forks), and a dry run sets `carried`, `sessionId`, `fork`, `files` and `bytes` with no `result`. After `continued` or `inconclusive` the sending agent ends its turn and makes no more changes on the branch. An OpenCode session travels as its export: `opencode export` on this machine, then `opencode import` inside the receiving repository there, and an OpenCode receiver is accepted only to continue an OpenCode session. A Cursor session is carried only to another machine, best effort since Cursor's store is undocumented: Cursor has no fork, and a copied chat still writes to the original, so on this machine the result is `not-continued` and nothing is started. A destination's agent store is assumed at its default location (`~/.claude`, `~/.codex`, `~/.cursor`); a copy already there is replaced only when the sender's holds all of it and no agent there runs it, otherwise nothing is copied and the result is `not-continued`. A destination Pane too old to resume a carried session is refused before anything is sent (`ERR_HANDOFF_RESUME_UNSUPPORTED`). From a Session's terminal it skips git and creates a Session with the same name, goal and agent on the other machine instead; a same-agent Session resumes the sender's conversation when first opened. The receiver reports back to the sender's panel.\",\n \"`runpane pr watch`: Register durable GitHub PR monitoring for a Pane.\",\n \"`runpane pr unwatch`: Remove a Pane’s explicit PR registration.\",\n \"`runpane pr list`: List PR registrations and stopped watches.\",\n \"`runpane autopilot status|waiting|stack` read Autopilot (experimental): its switch, run and stack, and its queue: every approval or question waiting on the person on every machine it can reach, grouped by Session, plus `offline` and `cantSee` machines. `runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next]` puts one entry on its stack; relative paths resolve against the caller's directory, and a new entry never replaces the one on screen. `runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) [--note <text>] --because <why>`, `runpane autopilot close <id> --reason <why>` and `runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>]` act on an item, on the machine that holds it, recorded \\\"by Autopilot\\\" on every device; the person can Override an answer and reopen a close. Only Autopilot may run them (`AUTOPILOT_ONLY`), and they refuse a permission prompt with `AUTOPILOT_CANNOT_ANSWER`. An item someone else already answered is refused with who did.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes ...`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. `agent-context` and `version` accept and ignore `--pane-dir`, so one `--pane-dir` can be passed to every runpane command. In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, use Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane`), which reaches the Windows named-pipe daemon through WSL interop; older Pane builds need `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'`.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers emit best-effort telemetry to `https://runpane.com/api/runpane/telemetry` for command failures, download request/success/failure, and GitHub fallback usage. Routine command starts and successes emit nothing. A command failure is a thrown error, or a non-zero exit from `install` or `update` (also when setup runs them); other exit codes are documented results (such as `panels wait` not ready or `doctor` findings) and emit nothing. Polling and status commands (`panes list`, `panels wait`, `watch`, `panes archive`, `workspace state`, `agents status`, `doctor`) emit no failure events, and neither does a `not_found` failure outside `install` and `update`. A failure event carries the command, `failure_stage`, `failure_category`, `exit_code` when known, and `error_code` (the code printed in the `--json` error). Both wrappers share a per-install limit in `<PANE_DIR>/runpane-wrapper-telemetry.json`: one failure event per cause per hour and at most 10 per hour.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so installs reporting failures or downloads can be counted with `count(DISTINCT properties.install_id)`; this is not a count of all CLI users.\",\n \"Wrapper telemetry must be disabled in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `TF_BUILD`, `JENKINS_URL`, `TEAMCITY_VERSION` or `CODEBUILD_BUILD_ID` set), when `DO_NOT_TRACK=1` or `RUNPANE_TELEMETRY_DISABLED` is set, and when the local config explicitly disables analytics. It must not include raw paths, labels, prompts, raw error text, or environment values.\",\n \"Desktop local control reports useful failures as `runpane_local_control_failed`; normal polling, wait deadlines, and optional agent unavailability emit nothing. Reports contain coarse operation and failure classification only, with a five-minute deduplication interval and a maximum of 20 per hour per app process. Handled CLI failures are ordinary events, not exceptions.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"doctor\",\n \"--report\",\n \"--title\",\n \"watch failed\",\n \"--body-file\",\n \"/tmp/watch-evidence.txt\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"monitor\",\n \"--from\",\n \"earliest\",\n \"--follow\",\n \"--heartbeat\",\n \"60s\",\n \"--idle-after\",\n \"600000\",\n \"--ready-settle\",\n \"180000\",\n \"--blocked-settle\",\n \"30000\",\n \"--min-interval\",\n \"600000\",\n \"--expire-after\",\n \"10800000\",\n \"--exclude-pane\",\n \"pane-old\",\n \"--kinds\",\n \"agent.ready,agent.idle\",\n \"--include-held-input\",\n \"--self-test\"\n ],\n [\n \"daemon\",\n \"repair\",\n \"--pane-dir\",\n \"/tmp/pane-remote\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"cost\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--pinned\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"pin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"unpin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"rename\",\n \"--pane\",\n \"session-1\",\n \"--name\",\n \"issue-393\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"focus\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"user\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"list\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"get\",\n \"--session\",\n \"demo\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"update\",\n \"--session\",\n \"demo\",\n \"--from-json\",\n \"-\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel=panel-1\",\n \"--text=- [ ] item\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"commit\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"fix: title\\n\\nbody\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"links\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--panel\",\n \"panel-1\",\n \"--json\"\n ],\n [\n \"links\",\n \"open\",\n \"--url=pane://open?pane=pane-1\",\n \"--yes\"\n ],\n [\n \"mcp\",\n \"--toolsets\",\n \"core,git\",\n \"--read-only\"\n ],\n [\n \"docs\",\n \"search\",\n \"--query\",\n \"archive a pane\",\n \"--limit\",\n \"3\"\n ],\n [\n \"docs\",\n \"read\",\n \"--doc\",\n \"docs/PANE_MCP.md\"\n ],\n [\n \"agents\",\n \"start\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"fix-login\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Fix the login redirect\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agents\",\n \"send\",\n \"--pane\",\n \"pane-1\",\n \"--text=- [ ] item\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--keys\",\n \"down,enter\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"squash-rebase\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"feat: land it\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"folders\",\n \"create\",\n \"--repo\",\n \"3\",\n \"--name\",\n \"Reviews\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"move\",\n \"--pane\",\n \"pane-1\",\n \"--folder\",\n \"folder-9\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"watch\",\n \"--follow\"\n ],\n [\n \"watch\",\n \"--session\",\n \"__orchestration_session_1b4e28ba-2fa1-11d2-883f-0016d3cca427__\",\n \"--follow\",\n \"--json\",\n \"--kinds\",\n \"agent.ready,agent.blocked,pane.associated,pane.detached,pr.conflicted,pr.checks,pr.merged\",\n \"--ready-settle\",\n \"180000\"\n ],\n [\n \"watch\",\n \"--session=Release train\",\n \"--as\",\n \"release-watch\",\n \"--exclude-pane\",\n \"pane-old\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\",\n \"--pane-dir=/tmp/pane\"\n ],\n [\n \"version\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--agent\",\n \"claude\",\n \"--base-branch\",\n \"origin/release\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--tool-command\",\n \"wrapper codex-run\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"farm\",\n \"--tool-command\",\n \"agent-farm run free-range\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Plan\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"long\",\n \"--agent\",\n \"claude\",\n \"--initial-input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"ready\",\n \"--pr\",\n \"747\",\n \"--head\",\n \"FC5DCE9\",\n \"--summary\",\n \"Done: tests pass\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"blocked\",\n \"--question\",\n \"Which API version?\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--summary-file\",\n \"-\"\n ],\n [\n \"report\",\n \"--state=done\",\n \"--summary-file=/tmp/report.md\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--all-stopped\",\n \"--wait-ready\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"last-message\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"5000\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation\",\n \"0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44\",\n \"--source\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation=abc\",\n \"--source=compact\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--hook\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--base\",\n \"release/foo\",\n \"--branch\",\n \"agents/w5a\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"prompt.md\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5b\",\n \"--base-branch\",\n \"origin/main\",\n \"--branch\",\n \"agents/w5b\",\n \"--worktree-name\",\n \"w5b-dir\",\n \"--tool-command\",\n \"bash\",\n \"--initial-input-file\",\n \"-\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--remove-worktree\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--remove-worktree\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"testing-account\",\n \"--ttl\",\n \"30m\",\n \"--wait\",\n \"1800000\",\n \"--note\",\n \"call QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"staging-db\",\n \"--ttl\",\n \"1500\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name=db\",\n \"--ttl=2h\"\n ],\n [\n \"lock\",\n \"release\",\n \"--name\",\n \"testing-account\",\n \"--force\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"list\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"--help\"\n ],\n [\n \"panels\",\n \"open\",\n \"--pane\",\n \"session-1\",\n \"--file\",\n \"plan.html\",\n \"--title\",\n \"Plan\",\n \"--split\",\n \"--no-focus\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"open\",\n \"--url\",\n \"http://localhost:3000\",\n \"--tab\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"correction\",\n \"--interrupt\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"repos\",\n \"get\",\n \"--repo\",\n \"Pane\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--build-script\",\n \"pnpm install\",\n \"--run-script\",\n \"pnpm dev\",\n \"--worktree-folder\",\n \"../wt\",\n \"--clear\",\n \"archive-script,system-prompt\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"orch\",\n \"--until-event\",\n \"--json\",\n \"--kinds\",\n \"agent.report,agent.blocked,panel.exited,pane.gone\",\n \"--timeout-ms\",\n \"600000\"\n ],\n [\n \"pr\",\n \"watch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"unwatch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"list\",\n \"--pane\",\n \"worker\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"0\"\n ],\n [\n \"watch\",\n \"--until-event\",\n \"--timeout-ms\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--timeout-ms\",\n \"off\"\n ],\n [\n \"watch\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"off\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--blocked-settle\",\n \"2s\",\n \"--min-interval\",\n \"1m\",\n \"--idle-after\",\n \"20m\",\n \"--expire-after\",\n \"off\",\n \"--heartbeat\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"go\",\n \"--expect-report\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"claude\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"codex\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"inbox\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"in-5e6f7a8b\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"--all\",\n \"--session\",\n \"Release\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"fyi\",\n \"--no-expect-report\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"prompt.md\",\n \"--as-file-pointer\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Red\",\n \"--wait\",\n \"--timeout\",\n \"30m\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"task.md\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"deploy\",\n \"--wait\",\n \"5000\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"nightly\",\n \"--cron\",\n \"0 3 * * *\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Sweep bugs\",\n \"--worktree-name\",\n \"nightly-sweep\",\n \"--paused\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo=2\",\n \"--name=deps\",\n \"--cron=0 9 * * 1\",\n \"--tool-command\",\n \"./scripts/nightly-report.sh\",\n \"--prompt-file\",\n \"-\"\n ],\n [\n \"loops\",\n \"create\",\n \"--session\",\n \"new\",\n \"--name\",\n \"triage\",\n \"--cron\",\n \"0 9 * * 1-5\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Triage\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--cron\",\n \"0 4 * * *\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--session\",\n \"Nightly\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"run\",\n \"--loop=loop-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"pause\",\n \"--loop\",\n \"loop-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"loops\",\n \"list\",\n \"--json\"\n ],\n [\n \"loops\",\n \"--help\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Ship it?\",\n \"--context\",\n \"Tests pass.\",\n \"--choices=[{\\\"label\\\":\\\"Yes\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"No\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--ui=approve\",\n \"--title=Drop the table?\",\n \"--detail=DROP TABLE t;\",\n \"--detail-label=Show the SQL\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which branch?\",\n \"--suggestions=[\\\"main\\\"]\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"info\",\n \"--title\",\n \"Done\",\n \"--action={\\\"label\\\":\\\"Open\\\",\\\"url\\\":\\\"https://example.com\\\"}\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"approve\",\n \"--title\",\n \"Ship the notes?\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR\\\",\\\"url\\\":\\\"https://example.com/pr/1\\\"}]\"\n ],\n [\n \"notify\",\n \"--resume\",\n \"n-1\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"list\",\n \"--project\",\n \"stripe\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"set\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\"\n ],\n [\n \"secrets\",\n \"delete\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"stripe\",\n \"--only\",\n \"A,B\",\n \"--env\",\n \"KEY=STRIPE_SECRET_KEY\",\n \"--env=TOKEN=GH_TOKEN\",\n \"--file\",\n \"CERT\",\n \"--\",\n \"node\",\n \"-e\",\n \"run()\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"qa\",\n \"sh\",\n \"-c\",\n \"echo ok\"\n ],\n [\n \"secrets\",\n \"request\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--title\",\n \"Stripe test key\",\n \"--reason\",\n \"- for the webhook tests\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--env\",\n \"[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"x\\\"}]\",\n \"--env-remove\",\n \"OLD,STALE\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"session-a\",\n \"--merged\",\n \"--closed\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"status\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"waiting\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--item\",\n \"n-1\",\n \"--note\",\n \"Needs a go\",\n \"--next\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--file\",\n \"/tmp/plan.html\",\n \"--title\",\n \"Plan\",\n \"--tab\",\n \"https://localhost:5173\",\n \"--tab=/tmp/notes.md\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--session\",\n \"Release\"\n ],\n [\n \"watch\",\n \"--as\",\n \"autopilot\",\n \"--kinds\",\n \"item.created,item.answered,session.created,session.archived,orchestrator.turn_ended\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"--decision\",\n \"D-147\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--relays\",\n \"in-1a2b3c4d\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Q-39: Run it?\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which?\",\n \"--about-session=Release\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--choice\",\n \"Yes\",\n \"--note\",\n \"Go\",\n \"--because\",\n \"D-22\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--approve\",\n \"--because=The person said so\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--deny\",\n \"--because\",\n \"D-23\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--text\",\n \"main\",\n \"--because\",\n \"D-24\"\n ],\n [\n \"autopilot\",\n \"close\",\n \"n-1\",\n \"--reason\",\n \"The PR merged\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"rewrite\",\n \"n-1\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Q-3: Which?\",\n \"--context\",\n \"Why\",\n \"--choices=[{\\\"label\\\":\\\"A\\\"}]\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"url\\\":\\\"https://example.com\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"answer\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took=n-1\",\n \"question\",\n \"--status\",\n \"Checking the logs\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"--took-as\",\n \"answer\"\n ],\n [\n \"notify\",\n \"--update\",\n \"Q-20\",\n \"--asked\",\n \"Can it be undone?\",\n \"--what-changed\",\n \"- Added: it can be undone\",\n \"--questions\",\n \"[{\\\"title\\\":\\\"A\\\",\\\"choices\\\":[{\\\"label\\\":\\\"x\\\"}]},{\\\"title\\\":\\\"B\\\",\\\"choices\\\":[{\\\"label\\\":\\\"y\\\"}]}]\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane repos rename\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\",\n \"runpane loops list\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\",\n \"python -m runpane loops list\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"description\": \"A failed command under --json, printed on stdout with a non-zero exit. The same message, plus a Next line, goes to stderr.\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\",\n \"description\": \"What happened, why, and what was or was not changed.\"\n },\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable identifier to branch on, such as ENOENT or ERR_UNKNOWN_CHANNEL. ERR_RUNPANE_FAILED when the failure has no specific code.\"\n },\n \"next\": {\n \"type\": \"string\",\n \"description\": \"The step to take next, when one is known.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteDaemonService\",\n \"remoteSetup\",\n \"watchDefaults\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteDaemonService\": {\n \"type\": \"object\",\n \"required\": [\n \"paneDir\",\n \"managed\",\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"managed\": {\n \"type\": \"boolean\"\n },\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"executableHealth\": {\n \"type\": \"object\",\n \"required\": [\n \"processImage\",\n \"restart\",\n \"checkedAt\"\n ],\n \"properties\": {\n \"processImage\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"runtimePath\",\n \"installedPath\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"current\",\n \"replaced\",\n \"deleted\",\n \"unknown\"\n ]\n },\n \"runtimePath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"installedPath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"restart\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"ready\",\n \"broken\",\n \"unknown\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"resolvedPath\": {\n \"type\": \"string\"\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"diagnosticCode\": {\n \"enum\": [\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n },\n \"checkedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\",\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"watchDefaults\": {\n \"type\": \"object\",\n \"required\": [\n \"follow\",\n \"untilEvent\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"follow\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"summary\",\n \"kinds\",\n \"readySettle\",\n \"blockedSettle\",\n \"minInterval\",\n \"idleAfter\",\n \"idleBackoff\",\n \"expireAfter\",\n \"heartbeat\",\n \"includeShells\",\n \"heldInputDetection\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"readySettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"blockedSettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"minInterval\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"expireAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"heartbeat\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"includeShells\": {\n \"type\": \"boolean\"\n },\n \"heldInputDetection\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"untilEvent\": {\n \"type\": \"object\",\n \"required\": [\n \"summary\",\n \"kinds\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n },\n \"cliInstalls\": {\n \"type\": \"object\",\n \"required\": [\n \"installs\",\n \"warnings\"\n ],\n \"properties\": {\n \"managedVersion\": {\n \"type\": \"string\"\n },\n \"installs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"origin\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"origin\": {\n \"enum\": [\n \"pane\",\n \"npm\",\n \"other\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"npmPrefix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"path\",\n \"message\",\n \"fix\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"RUNPANE_STALE_INSTALL\",\n \"RUNPANE_STALE_UNMANAGED\",\n \"RUNPANE_STALE_MANAGED\",\n \"RUNPANE_MANAGED_BEHIND\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"fixed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"ok\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"workspacesSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"machine\",\n \"otherMachines\",\n \"lines\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"This machine's Tailscale name.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"description\": \"Why workspaces are off.\"\n },\n \"fix\": {\n \"type\": \"string\",\n \"description\": \"The one step that turns them on.\"\n },\n \"otherMachines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"os\",\n \"online\",\n \"owner\",\n \"mine\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own Tailscale login.\"\n }\n }\n }\n },\n \"lines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The block printed by help, doctor, and agent-context.\"\n }\n }\n },\n \"daemonRepairResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"paneDir\",\n \"strategy\",\n \"launcherPath\",\n \"before\",\n \"after\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"systemd-user\",\n \"launch-agent\",\n \"scheduled-task\",\n \"manual\",\n \"skipped\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"before\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"after\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"scriptSource\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"source\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"scriptSetting\": {\n \"type\": \"object\",\n \"required\": [\n \"value\",\n \"fallback\",\n \"effective\"\n ],\n \"properties\": {\n \"value\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"fallback\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"effective\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"settings\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"currentBranch\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"configFile\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"envVars\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"currentBranch\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"configFile\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"buildScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"runScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"archiveScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"envVars\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"repoUpdateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n },\n \"settings\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"buildScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"runScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"archiveScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"envVars\": {\n \"type\": \"object\",\n \"description\": \"set adds or replaces variables by key; remove deletes them by key.\",\n \"minProperties\": 1,\n \"properties\": {\n \"set\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n },\n \"remove\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoUpdateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"changed\": {\n \"type\": \"array\",\n \"items\": {\n \"enum\": [\n \"name\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"envVars\"\n ]\n }\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"name\"\n ],\n \"properties\": {\n \"repo\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"updatedAt\",\n \"machine\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"type\": \"string\",\n \"description\": \"ISO time it was last set.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine where it was last set.\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"secrets\",\n \"storage\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"secrets\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"storage\": {\n \"type\": \"object\",\n \"description\": \"Where this machine keeps values. encrypted is false only for the file store on a machine without an OS keyring.\",\n \"required\": [\n \"kind\",\n \"encrypted\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"keychain\",\n \"file\"\n ]\n },\n \"encrypted\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetRequest\": {\n \"description\": \"What `runpane secrets set` sends on runpane:secrets:set. value comes from stdin.\",\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"value\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"value\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"replaced\",\n \"secret\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"replaced\": {\n \"type\": \"boolean\"\n },\n \"secret\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"projectEnvVar\": {\n \"description\": \"A project environment variable. Plain values are shown in full; keychain and Pane secret variables are references, never values.\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"key\",\n \"source\",\n \"value\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"plain\"\n },\n \"value\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"An existing macOS keychain generic-password item.\",\n \"required\": [\n \"key\",\n \"source\",\n \"service\",\n \"account\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"keychain\"\n },\n \"service\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"account\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The Pane secret project/name.\",\n \"required\": [\n \"key\",\n \"source\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"secret\"\n },\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"branch\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\",\n \"cursor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"associateSession\": {\n \"type\": \"string\",\n \"description\": \"Session id to associate each created Pane with; the CLI sets it from PANE_ORCHESTRATION_SESSION_ID unless --no-associate.\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"staged\": {\n \"type\": \"boolean\"\n },\n \"attempts\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"association\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"ok\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"setupQueue\": {\n \"type\": \"object\",\n \"required\": [\n \"position\",\n \"message\"\n ],\n \"properties\": {\n \"position\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"required\": [\n \"ref\",\n \"refresh\"\n ],\n \"properties\": {\n \"ref\": {\n \"type\": \"string\"\n },\n \"commit\": {\n \"type\": \"string\"\n },\n \"refresh\": {\n \"type\": \"string\",\n \"enum\": [\n \"fetched\",\n \"recent\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"notice\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\",\n \"pinned\",\n \"agentStatus\",\n \"ownership\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"inactiveSince\": {\n \"type\": \"string\",\n \"description\": \"When the Pane was last used: the latest terminal output or input in its tabs, or the last time the person selected it (ISO 8601).\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\",\n \"description\": \"Set while every terminal tab of the Pane is paused: the latest tab's mark.\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"agentStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"agentState\": {\n \"enum\": [\n \"ready\",\n \"working\",\n \"blocked\",\n \"none\"\n ]\n },\n \"ownership\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostResult\": {\n \"$defs\": {\n \"usageTotals\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"usageByModel\": {\n \"type\": \"object\",\n \"required\": [\n \"model\",\n \"provider\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"model\": {\n \"type\": \"string\"\n },\n \"provider\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostSlice\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"pane\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"paneName\",\n \"worktreePath\",\n \"repoId\",\n \"archived\",\n \"createdAtMs\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"fromMs\",\n \"toMs\",\n \"pricingAsOf\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"fromMs\": {\n \"type\": \"number\"\n },\n \"toMs\": {\n \"type\": \"number\"\n },\n \"pricingAsOf\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/pane\"\n }\n },\n \"unattributed\": {\n \"$ref\": \"#/$defs/paneCostSlice\"\n },\n \"totals\": {\n \"$ref\": \"#/$defs/usageTotals\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"gen\",\n \"at\",\n \"kind\",\n \"paneId\",\n \"paneName\",\n \"source\"\n ],\n \"properties\": {\n \"gen\": {\n \"type\": \"number\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"agent.ready\",\n \"agent.busy\",\n \"agent.blocked\",\n \"agent.unknown\",\n \"agent.idle\",\n \"pane.created\",\n \"pane.gone\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"agent.report\",\n \"agent.report.missing\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\",\n \"item.created\",\n \"item.answered\",\n \"session.created\",\n \"session.archived\",\n \"orchestrator.turn_ended\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"to\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"source\": {\n \"enum\": [\n \"agent\",\n \"exit\",\n \"session\",\n \"github\"\n ]\n },\n \"reason\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"settledMs\": {\n \"type\": \"number\"\n },\n \"idleMs\": {\n \"type\": \"number\"\n },\n \"idleCount\": {\n \"type\": \"number\"\n },\n \"heldInput\": {\n \"type\": \"string\"\n },\n \"heldInputPresent\": {\n \"type\": \"boolean\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Baseline agent entries: whether the panel's terminal process is live. A stopped panel is never agent.ready.\"\n },\n \"baseline\": {\n \"const\": true\n },\n \"replay\": {\n \"const\": true,\n \"description\": \"Set on baseline entries delivered after a reset. A replayed entry restates current state and is never a new transition: a replayed agent.ready is not READY.\"\n },\n \"changedWhileAway\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Named Session of a pane.associated or pane.detached entry.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"description\": \"PR of a pr.conflicted, pr.checks, or pr.merged entry.\",\n \"required\": [\n \"number\",\n \"url\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"description\": \"Base branch of a pr.base_changed (old to new) or pr.base_deleted (to is the PR's current base name) entry.\",\n \"required\": [\n \"from\",\n \"to\"\n ],\n \"properties\": {\n \"from\": {\n \"type\": \"string\"\n },\n \"to\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"checks\": {\n \"enum\": [\n \"passed\",\n \"failed\"\n ],\n \"description\": \"Failures are immediate; success requires complete nonempty required checks on the pinned head.\"\n },\n \"failingChecks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Newly failing check names on this head.\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\",\n \"description\": \"Report of an agent.report entry; its summary is cut to 2,000 characters (the panel keeps up to 16,000).\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Added to watch --json NDJSON events. Persist with gen after processing for numeric rearm.\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentState\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"running\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"lastOutput\": {\n \"type\": \"string\",\n \"description\": \"What an agent.report.missing entry's agent said last, up to 2,000 characters (the end is kept).\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ],\n \"description\": \"transcript: the agent's last reply; screen: the last lines on its screen.\"\n },\n \"expectedSince\": {\n \"type\": \"string\",\n \"description\": \"When the work an agent.report.missing entry is about was sent.\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The screen lines of the prompt an agent.blocked entry's agent waits on (at most 20).\"\n },\n \"itemId\": {\n \"type\": \"string\",\n \"description\": \"The Notify item of an item.created or item.answered entry; `runpane autopilot stack push --item <id>` takes it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentReport\": {\n \"type\": \"object\",\n \"description\": \"The latest runpane report from an agent panel.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelReport\": {\n \"type\": \"object\",\n \"description\": \"A panel's latest runpane report in sessions overview. data is the summary when it is a complete JSON object, returned as the worker gave it; a plain-text, array, scalar, invalid, more than 64 levels deep, or truncated summary has no data.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n },\n \"data\": {\n \"type\": \"object\",\n \"description\": \"The worker's JSON object summary, returned as given. Pane never reads, renames or validates its fields.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"since\": {\n \"type\": \"number\"\n },\n \"as\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"now\",\n \"earliest\"\n ]\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry/properties/kind\"\n }\n },\n \"paneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Named Session id or exact name. Limits the wait to the Session's associated Panes, resolved on every read, and implies the opt-in pane.associated, pane.detached, and pr.* entries. Cannot be combined with paneIds.\"\n },\n \"excludePaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"nameContains\": {\n \"type\": \"string\"\n },\n \"agentsOnly\": {\n \"type\": \"boolean\"\n },\n \"ackNow\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInput\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInputPresence\": {\n \"type\": \"boolean\"\n },\n \"idleAfterMs\": {\n \"type\": \"number\"\n },\n \"idleWindowStartMs\": {\n \"type\": \"number\"\n },\n \"settleMs\": {\n \"type\": \"number\"\n },\n \"blockedSettleMs\": {\n \"type\": \"number\"\n },\n \"minIntervalMs\": {\n \"type\": \"number\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Expected epoch for since. Requires since and cannot be combined with as. Mismatch returns epoch-changed reset.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceStateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\",\n \"timedOut\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"dropped\": {\n \"type\": \"number\"\n },\n \"reset\": {\n \"type\": \"object\",\n \"required\": [\n \"reason\"\n ],\n \"properties\": {\n \"reason\": {\n \"enum\": [\n \"first-use\",\n \"epoch-changed\",\n \"cursor-truncated\",\n \"unknown-consumer\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"session\": {\n \"type\": \"object\",\n \"description\": \"The Session a session request resolved to. A client that sent session and gets no session back is talking to a daemon that ignored it.\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"activityGeneration\": {\n \"type\": \"number\",\n \"description\": \"Latest meaningful activity in scope, including events hidden by output kinds or cadence. Natural expiry resets when it advances.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"favoritePinnedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"pane\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"pane\": {\n \"$ref\": \"#/jsonSchemas/paneListResult/properties/panes/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"focused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"focused\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ],\n \"description\": \"`completed`: the worktree is gone from its path and from git (see trashDeletion). `failed`: removal failed and the worktree may remain; ok is false. `timeout`: the archive script or git removal is still running in the background; ok stays true. `not-applicable`: nothing was removed (main-repo Pane, or an adopted worktree without --remove-worktree). Released CLIs decode exactly these values.\"\n },\n \"trashDeletion\": {\n \"enum\": [\n \"pending\",\n \"done\"\n ],\n \"description\": \"Present with worktreeCleanup `completed`. `pending`: the removed worktree was moved into the repository's .git/pane-trash and its files are still being deleted in the background. `done`: the files are gone.\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"upstreamGone\": {\n \"type\": \"boolean\",\n \"description\": \"The branch had an upstream that no longer exists on the remote, so commits were compared with the base branch.\"\n },\n \"mergedViaPr\": {\n \"type\": \"object\",\n \"description\": \"A merged pull request whose head commit is HEAD. Its commits are not counted as unpushed.\",\n \"required\": [\n \"number\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"busyAgents\": {\n \"type\": \"array\",\n \"description\": \"Agents in the Pane that are mid-turn (working) or waiting for an answer (blocked). Archiving would stop them, so archive refuses with agent-busy unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"state\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"enum\": [\n \"working\",\n \"blocked\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"unsentInput\": {\n \"type\": \"array\",\n \"description\": \"Tabs holding input a person typed that archiving would lose: an agent composer's unsent text or an editor's unsaved edits. Archive refuses with unsent-input unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"kind\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"composer\",\n \"editor\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"dryRun\",\n \"wouldArchive\",\n \"forced\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"wouldArchive\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/2/properties/blocked\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"busyAgents\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/busyAgents\"\n },\n \"unsentInput\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/unsentInput\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"dock\": {\n \"type\": \"boolean\",\n \"description\": \"True on the Pane's terminal dock (its plain shell); omitted on every other panel.\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"resumeId\": {\n \"type\": \"string\"\n },\n \"resumeSource\": {\n \"enum\": [\n \"agent-farm\",\n \"configured\",\n \"agent\"\n ]\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n },\n \"openedBy\": {\n \"type\": \"object\",\n \"description\": \"Who opened this tab through runpane, and the tab that opened it when known. Omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose source is agent.\",\n \"required\": [\n \"source\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"agent\",\n \"user\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\",\n \"composer\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"composer\": {\n \"type\": \"object\",\n \"required\": [\n \"isPresent\",\n \"hasUndeliveredText\"\n ],\n \"properties\": {\n \"isPresent\": {\n \"type\": \"boolean\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"ghostText\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"asFilePointer\": {\n \"type\": \"boolean\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"interrupt\": {\n \"type\": \"boolean\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n },\n \"operationId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"enum\": [\n \"cr\",\n \"tab\",\n \"none\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"acp\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"evidence\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"taken\",\n \"queued\",\n \"in-composer\",\n \"unknown\"\n ]\n },\n \"evidence\": {\n \"enum\": [\n \"transcript\",\n \"screen\",\n \"argv\",\n \"acp-receipt\"\n ]\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human explanation of delivery; queued means the agent has not read the message yet.\"\n }\n },\n \"additionalProperties\": false\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitComposerResult/properties/blocked\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"leading-bang-runs-shell\",\n \"leading-hash-memory\",\n \"leading-slash-command\",\n \"leading-at-mention\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n },\n \"operationId\": {\n \"type\": \"string\",\n \"description\": \"Durable host operation ID for Chat delivery reconciliation.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"stopped\": {\n \"const\": true,\n \"description\": \"The wait did not match and the panel's terminal is not running (ready and idle waits return this after a 2-second grace instead of timing out); nextCommand is `runpane panels resume`.\"\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"action\",\n \"message\",\n \"panel\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ],\n \"description\": \"resumed: the panel's terminal was restarted in place. already-running: nothing was restarted.\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\",\n \"description\": \"The agent conversation the panel resumes, when Pane knows it.\"\n },\n \"panel\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeManyResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"scope\",\n \"resumed\",\n \"alreadyRunning\",\n \"failed\",\n \"notReady\",\n \"concurrency\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"Every stopped panel in scope resumed and, with --wait-ready, became ready.\"\n },\n \"scope\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\",\n \"sessionName\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"all-stopped\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"resumed\": {\n \"type\": \"number\"\n },\n \"alreadyRunning\": {\n \"type\": \"number\",\n \"description\": \"Agent panels in scope that were already running and were left alone.\"\n },\n \"failed\": {\n \"type\": \"number\",\n \"description\": \"Panels that could not be restarted; their item carries error.\"\n },\n \"notReady\": {\n \"type\": \"number\",\n \"description\": \"With --wait-ready: resumed panels that were blocked or not ready within --ready-timeout-ms.\"\n },\n \"concurrency\": {\n \"type\": \"number\",\n \"description\": \"Panels restarted at once.\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"paneName\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why this panel could not be resumed; the others still were.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelLastMessageResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"agentType\",\n \"text\",\n \"length\",\n \"limit\",\n \"truncated\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"length\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"truncated\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"reason\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"reason\": {\n \"const\": \"transcript-unavailable\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelConversationRequest\": {\n \"type\": \"object\",\n \"description\": \"What runpane panels conversation sends the daemon (runpane:panels:conversation).\",\n \"required\": [\n \"panelId\",\n \"conversationId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"conversationId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 256\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelConversationResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"conversationId\",\n \"sure\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"conversationId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n },\n \"sure\": {\n \"type\": \"boolean\",\n \"description\": \"True when this report came since the panel's agent started, so Pane knows the panel is on this conversation.\"\n },\n \"generation\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"type\",\n \"title\",\n \"placement\",\n \"active\",\n \"reused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"enum\": [\n \"browser\",\n \"editor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"reused\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\",\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n \"force\": {\n \"type\": \"boolean\",\n \"description\": \"Close it as the tab's close button does, skipping every check but permanent: for a stuck tab, or one the person asked to close. A busy agent's turn and unsent input are lost.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"closed\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when the close was refused (the tab stays open); the CLI exits 1.\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\",\n \"description\": \"Omitted only when the tab was already gone.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"The tab's type, such as terminal, chat, browser or editor. Omitted only when the tab was already gone.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"closed\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\",\n \"description\": \"Closed with --force, skipping the checks that would have kept it.\"\n },\n \"alreadyClosed\": {\n \"type\": \"boolean\",\n \"description\": \"No tab has this id: it was closed already.\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"wouldClose\": {\n \"type\": \"boolean\",\n \"description\": \"Dry run only: whether the close would go ahead.\"\n },\n \"refused\": {\n \"type\": \"object\",\n \"description\": \"Why the tab was kept open. opened-by-user: not opened by an agent with --source agent (including tabs from before Pane recorded openers). permanent: a permanent tab. agent-busy: its agent is mid-turn or waiting for an answer. unsent-input: unsent composer text or unsaved editor edits. input-unknown: a live tab whose input Pane cannot read.\",\n \"required\": [\n \"code\",\n \"message\",\n \"next\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"opened-by-user\",\n \"permanent\",\n \"agent-busy\",\n \"unsent-input\",\n \"input-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"composer-unknown\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"sessionListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessions\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"selectedSessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"internalSessionId\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"object\",\n \"description\": \"Only when the update archived the Session: what happened to each associated Pane.\",\n \"required\": [\n \"archivedPaneIds\",\n \"keptPanes\",\n \"failedPanes\"\n ],\n \"properties\": {\n \"archivedPaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"keptPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"code\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"failedPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"error\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"sessionOverviewResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"status\",\n \"panes\",\n \"activity\",\n \"refreshedAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/panelReport\"\n }\n }\n }\n }\n }\n }\n },\n \"activity\": {\n \"type\": \"array\"\n },\n \"refreshedAt\": {\n \"type\": \"string\"\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"locks\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"scope\",\n \"owner\",\n \"acquiredAt\",\n \"expiresAt\",\n \"ttlMs\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"scope\": {\n \"enum\": [\n \"session\",\n \"global\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"acquiredAt\": {\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"ttlMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ttlMs\",\n \"owner\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"ttlMs\": {\n \"type\": \"number\"\n },\n \"waitMs\": {\n \"type\": \"number\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"renewed\",\n \"waitedMs\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acquired\": {\n \"const\": true\n },\n \"renewed\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"timedOut\",\n \"waitedMs\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"acquired\": {\n \"const\": false\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"lockReleaseResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"forced\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"released\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"reason\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"released\": {\n \"const\": false\n },\n \"reason\": {\n \"const\": \"not-owner\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"daemonActionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"data\": {},\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"link\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"linkCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"url\",\n \"target\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"target\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"repo\",\n \"session\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"docsSearchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"query\",\n \"results\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"query\": {\n \"type\": \"string\"\n },\n \"results\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"title\",\n \"kind\",\n \"excerpt\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"excerpt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"docsReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"path\",\n \"title\",\n \"kind\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentTaskResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"link\",\n \"ready\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"promptDelivered\": {\n \"type\": \"boolean\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"status\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"working\",\n \"ready\",\n \"blocked\",\n \"idle\",\n \"exited\",\n \"unknown\"\n ]\n },\n \"screen\": {\n \"type\": \"string\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentSendResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"delivered\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"blocked\": {\n \"type\": \"string\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextUnknownCommandError\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"code\",\n \"message\",\n \"candidates\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"code\": {\n \"const\": \"unknown_command\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"candidates\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"reportResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"report\",\n \"sessionIds\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"sessionIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"liveWatchers\": {\n \"type\": \"number\",\n \"description\": \"Watches that were waiting for this report when it was journaled and received it. A snapshot: a named-cursor watch that waits later still receives it. Absent from older daemons.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"merged\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"type\": \"boolean\",\n \"description\": \"Also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessionId\",\n \"merged\",\n \"removeWorktree\",\n \"archived\",\n \"skipped\",\n \"failed\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n },\n \"archived\": {\n \"type\": \"number\",\n \"description\": \"Panes archived, or that would be archived with --dry-run.\"\n },\n \"skipped\": {\n \"type\": \"number\"\n },\n \"failed\": {\n \"type\": \"number\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"outcome\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"outcome\": {\n \"enum\": [\n \"archived\",\n \"would-archive\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"skipped\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\",\n \"missing-pane\",\n \"already-archived\",\n \"main-repo\",\n \"pr-open\",\n \"pr-not-merged\",\n \"pr-status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"open\",\n \"merged\",\n \"closed\",\n \"none\",\n \"unknown\"\n ]\n },\n \"number\": {\n \"type\": \"number\"\n },\n \"reason\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"worktreeCleanup\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/worktreeCleanup\"\n },\n \"trashDeletion\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/trashDeletion\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"prWatchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"watch\": {\n \"type\": \"object\"\n },\n \"watches\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"removed\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"inboxListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"sessionId\",\n \"kind\",\n \"at\",\n \"paneId\",\n \"paneName\",\n \"panelId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"report\",\n \"report.missing\",\n \"blocked\"\n ]\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"answers\": {\n \"type\": \"array\",\n \"description\": \"The answers the Session's workers got, kept 7 days. Absent from an older Pane.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"answer\",\n \"by\",\n \"worker\",\n \"answeredAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"answer\": {\n \"type\": \"string\"\n },\n \"by\": {\n \"enum\": [\n \"you\",\n \"autopilot\",\n \"orchestrator\",\n \"pane\"\n ],\n \"description\": \"Who answered.\"\n },\n \"because\": {\n \"type\": \"string\"\n },\n \"worker\": {\n \"type\": \"string\",\n \"description\": \"The worker that asked.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"answeredAt\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"enum\": [\n \"call\",\n \"tab\"\n ],\n \"description\": \"How it reached the worker; absent while its tab is busy.\"\n },\n \"undelivered\": {\n \"type\": \"string\",\n \"description\": \"Why Pane can't type it into the worker's tab (its tab closed, or Pane can't see what runs there): pass it on yourself.\"\n },\n \"decision\": {\n \"type\": \"string\",\n \"description\": \"The D-n recorded for it; absent: run `runpane inbox ack <id> --decision <D-n>`.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"inboxAckResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acknowledged\",\n \"unknown\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acknowledged\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"unknown\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"dispatchWaitResult\": {\n \"type\": \"object\",\n \"description\": \"What `--wait` prints, as one line: how the dispatched work ended. Exit codes: 0 report, 3 missing, 4 blocked, 5 exited or gone, 124 timeout. Exit 6 is indeterminate after journal replacement or history loss; inspect the panel and inbox before retrying.\",\n \"required\": [\n \"ok\",\n \"outcome\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"true only for outcome report.\"\n },\n \"outcome\": {\n \"enum\": [\n \"report\",\n \"missing\",\n \"blocked\",\n \"exited\",\n \"gone\",\n \"timeout\",\n \"indeterminate\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\",\n \"description\": \"The journal entry that ended the wait (on timeout, the position reached).\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"nextCommand\": {\n \"type\": \"string\",\n \"description\": \"Recovery command when the outcome is indeterminate.\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopInput\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"cron\",\n \"enabled\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"loop\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"cron\",\n \"enabled\",\n \"createdAtMs\",\n \"nextRunAtMs\",\n \"scheduleText\",\n \"runs\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"nextRunAtMs\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"runs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"loopId\",\n \"slotAtMs\",\n \"trigger\",\n \"status\",\n \"error\",\n \"sessionId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"loopId\": {\n \"type\": \"string\"\n },\n \"slotAtMs\": {\n \"type\": \"number\"\n },\n \"trigger\": {\n \"enum\": [\n \"schedule\",\n \"manual\"\n ]\n },\n \"status\": {\n \"enum\": [\n \"started\",\n \"failed\",\n \"skipped\"\n ]\n },\n \"error\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"sessionId\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loops\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loops\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loop\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loop\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDryRunResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"dryRun\",\n \"input\",\n \"scheduleText\",\n \"nextRunAtMs\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"input\": {\n \"$ref\": \"#/jsonSchemas/loopInput\"\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"nextRunAtMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyChoice\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The answer notify returns when the person picks this choice.\"\n },\n \"detail\": {\n \"type\": \"string\",\n \"description\": \"One more line about this choice.\"\n },\n \"recommended\": {\n \"type\": \"boolean\",\n \"description\": \"Shade this choice as the one you recommend.\"\n },\n \"was\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"--update only: the label this choice had before it was reworded. A choice whose label stays but whose detail changed may repeat its label here.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifySubQuestion\": {\n \"description\": \"One question of an options approval that asks several at once; the person picks one choice.\",\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"choices\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"notifyAction\": {\n \"type\": \"object\",\n \"required\": [\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"url\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"notifyLink\": {\n \"description\": \"A related file, page or approval shown as a chip on the card: a label and exactly one of path, url or approval.\",\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"minProperties\": 2,\n \"maxProperties\": 2,\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 80,\n \"description\": \"The chip's text.\"\n },\n \"path\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A file on the asking machine; a relative path resolves against the caller's working directory.\"\n },\n \"url\": {\n \"type\": \"string\",\n \"pattern\": \"^https?://\\\\S+$\",\n \"description\": \"An http or https page.\"\n },\n \"approval\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Another approval, by its id or Q-n; it opens in place, with Back.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyRequest\": {\n \"description\": \"What `runpane notify` and `runpane secrets request` send on the runpane:notify daemon channel: the question fields, with relays or aboutSession when they apply; resume alone; took with tookAs (and status); or update with asked, whatChanged and the changed fields. Only `secrets request` sends ui secret.\",\n \"type\": \"object\",\n \"required\": [\n \"caller\"\n ],\n \"properties\": {\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"secret\",\n \"info\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"questions\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n },\n \"description\": \"ui options, instead of choices: several questions, one pick each.\"\n },\n \"detail\": {\n \"type\": \"string\"\n },\n \"detailLabel\": {\n \"type\": \"string\",\n \"maxLength\": 40\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"action\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n },\n \"secret\": {\n \"type\": \"object\",\n \"description\": \"ui secret only: the Pane secret the answer is saved as.\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n },\n \"resume\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"took\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) whose reply the reader took; sent with tookAs and nothing but status.\"\n },\n \"tookAs\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ],\n \"description\": \"answer records the reply as the answer; question shows the reader is getting more context.\"\n },\n \"status\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"tookAs question: what the reader is doing, shown on the approval.\"\n },\n \"update\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) to save a new version of; the question fields sent replace those fields. No ui.\"\n },\n \"asked\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: the person's question, as they typed it.\"\n },\n \"whatChanged\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: what changed, one line per change.\"\n },\n \"relays\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A waiting item (an id from `runpane inbox`) to improve instead of posting a second card. ui and title are still required.\"\n },\n \"aboutSession\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Autopilot's own item: the Session it is about, an id or exact name.\"\n },\n \"returnAfterMs\": {\n \"type\": \"number\",\n \"description\": \"Return waiting after this long; set for MCP clients whose tool calls time out.\"\n },\n \"caller\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"notifyResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"answer\"\n ],\n \"properties\": {\n \"answer\": {\n \"type\": \"string\",\n \"description\": \"options: the chosen label (with questions, the picks joined with \\\" · \\\"). ask: the typed answer.\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n },\n \"answers\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"options with questions: one pick per question, in order.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The person typed a reply: it may answer the question or ask one. The approval keeps waiting; say which with `runpane notify --took <replyId> answer|question` (see next).\",\n \"required\": [\n \"reply\",\n \"id\",\n \"replyId\",\n \"next\"\n ],\n \"properties\": {\n \"reply\": {\n \"type\": \"string\",\n \"description\": \"What the person typed.\"\n },\n \"picks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"What they picked with it, one label per question.\"\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval, for --took and --update.\"\n },\n \"replyId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"This call's private id for the reply (not the id other screens show). Pass it to --took and --update: only this call was given it, so it lets its agent continue, also from outside a Pane tab.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"--took: how the reply was recorded.\",\n \"required\": [\n \"took\",\n \"id\"\n ],\n \"properties\": {\n \"took\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"delivered\"\n ],\n \"properties\": {\n \"delivered\": {\n \"enum\": [\n \"screen\",\n \"inbox\"\n ],\n \"description\": \"screen: shown on the strip. inbox: listed in the Notify inbox because a question was on screen.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person saved the value as project/name. The value is never returned.\",\n \"required\": [\n \"status\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"saved\"\n },\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person declined; nothing was saved.\",\n \"required\": [\n \"status\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"declined\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"waiting\",\n \"id\",\n \"next\"\n ],\n \"properties\": {\n \"waiting\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"unavailable\",\n \"next\"\n ],\n \"properties\": {\n \"unavailable\": {\n \"type\": \"string\",\n \"description\": \"Why nobody can answer through Pane.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"workspaceMachine\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"dnsName\",\n \"os\",\n \"online\",\n \"ips\",\n \"self\",\n \"owner\",\n \"mine\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Tailscale machine name; pass it as `machine`.\"\n },\n \"dnsName\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"ips\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"self\": {\n \"type\": \"boolean\",\n \"description\": \"This machine.\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own login.\"\n }\n }\n },\n \"workspaceListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"self\",\n \"machines\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"self\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"properties\": {\n \"workspace\": {\n \"description\": \"This machine's workspace state; null when Pane is not running.\",\n \"anyOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"enabled\",\n \"state\"\n ],\n \"properties\": {\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n },\n \"machineName\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n }\n },\n \"machines\": {\n \"type\": \"array\",\n \"items\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"required\": [\n \"joined\"\n ],\n \"properties\": {\n \"joined\": {\n \"type\": \"boolean\",\n \"description\": \"Its Pane answers workspace read, write, and exec.\"\n },\n \"info\": {\n \"type\": \"object\",\n \"required\": [\n \"hostname\",\n \"os\",\n \"isWsl\",\n \"shell\",\n \"homeDir\",\n \"wslDistros\"\n ],\n \"properties\": {\n \"hostname\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"isWsl\": {\n \"type\": \"boolean\"\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"homeDir\": {\n \"type\": \"string\"\n },\n \"wslDistros\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why it does not answer.\"\n }\n }\n }\n }\n }\n },\n \"workspaceReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"encoding\",\n \"content\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine read from; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"encoding\": {\n \"enum\": [\n \"utf8\",\n \"base64\"\n ]\n },\n \"content\": {\n \"type\": \"string\",\n \"description\": \"File text, or base64 bytes; a directory lists its entries.\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceWriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine written to; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceExecResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"os\",\n \"shell\",\n \"cwd\",\n \"exitCode\",\n \"signal\",\n \"timedOut\",\n \"stdout\",\n \"stderr\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"The command exited 0.\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"signal\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"stillRunning\": {\n \"const\": true,\n \"description\": \"It timed out and could not be stopped.\"\n },\n \"stdout\": {\n \"type\": \"string\"\n },\n \"stderr\": {\n \"type\": \"string\"\n }\n }\n },\n \"autopilotStackEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"ref\",\n \"title\",\n \"tabs\",\n \"addedAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"ref\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"description\": \"The first tab is the entry's own (its approval, document, URL, or Session).\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"addedAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"About something Autopilot wrote or asked itself.\"\n },\n \"doneAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms when it was resolved (answered, expired, or dismissed).\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"One entry on Autopilot's stack. References only: the item, file, URL, or Session it is about.\"\n },\n \"autopilotStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"enabled\",\n \"chatTabsEnabled\",\n \"entitlement\",\n \"run\",\n \"panelId\",\n \"ownerPaneId\",\n \"state\",\n \"sentinel\",\n \"stack\",\n \"walk\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"enabled\": {\n \"type\": \"boolean\",\n \"description\": \"The experimental switch (Settings › Advanced › Experimental › Autopilot).\"\n },\n \"chatTabsEnabled\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot runs as a chat tab, so it needs chat tabs on.\"\n },\n \"entitlement\": {\n \"type\": \"object\",\n \"required\": [\n \"entitled\"\n ],\n \"properties\": {\n \"entitled\": {\n \"type\": \"boolean\"\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"run\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"folder\",\n \"docsFolder\",\n \"createdAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"folder\": {\n \"type\": \"string\"\n },\n \"docsFolder\": {\n \"type\": \"string\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"panelId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"Autopilot's chat tab, once the run has one.\"\n },\n \"ownerPaneId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"state\": {\n \"enum\": [\n \"stopped\",\n \"running\",\n \"paused\"\n ]\n },\n \"sentinel\": {\n \"type\": \"boolean\",\n \"description\": \"Sentinel Mode: Autopilot keeps Sessions moving while the person is away (turning it on also starts Autopilot).\"\n },\n \"sentinelSince\": {\n \"type\": \"number\",\n \"description\": \"Epoch milliseconds when Sentinel was turned on; absent while it is off.\"\n },\n \"stack\": {\n \"type\": \"object\",\n \"required\": [\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"walk\": {\n \"type\": \"object\",\n \"required\": [\n \"sessions\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"The Session the walk is on.\"\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"waiting\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"waiting\": {\n \"type\": \"number\",\n \"description\": \"Items waiting on the person in this Session.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotWaitingResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Grouped by Session, top first, in the order Autopilot walks them.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"kind\",\n \"agent\",\n \"pane\",\n \"links\",\n \"createdAt\",\n \"ageMs\",\n \"machine\",\n \"machineId\",\n \"onStack\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"approval\",\n \"question\",\n \"permission\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Absent: not in a Session.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n },\n \"pane\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"orchestrator\": {\n \"type\": \"boolean\",\n \"description\": \"The asking tab is the Session's orchestrator.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot asked it itself.\"\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"createdAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"ageMs\": {\n \"type\": \"number\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"machineId\": {\n \"type\": \"string\"\n },\n \"onStack\": {\n \"type\": \"boolean\",\n \"description\": \"Already on the stack.\"\n },\n \"quiet\": {\n \"type\": \"boolean\",\n \"description\": \"A worker's question its orchestrator still has first call on: it shows, but doesn't ring yet.\"\n },\n \"stale\": {\n \"type\": \"boolean\",\n \"description\": \"Unanswered for 3 days: it never rings or pushes again.\"\n },\n \"connected\": {\n \"type\": \"object\",\n \"description\": \"What the item is connected to: its Session, or outside one its Pane, Autopilot's chat or its machine. Absent from an older machine.\",\n \"required\": [\n \"kind\",\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"session\",\n \"pane\",\n \"autopilot\",\n \"machine\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n },\n \"offline\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Saved machines it can't reach. Absent from an older Pane.\"\n },\n \"cantSee\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The person's Tailscale machines not saved here, so their items can't show. Absent from an older Pane.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackPushRequest\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"item\": {\n \"type\": \"string\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Absolute: runpane resolves a relative path against the caller's directory.\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Session id or exact name.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"next\": {\n \"type\": \"boolean\",\n \"description\": \"Right after the entry on screen, so it comes up next.\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"Exactly one of item, file, url, or session.\"\n },\n \"autopilotStackPushResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entry\",\n \"shown\",\n \"position\",\n \"total\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entry\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"shown\": {\n \"type\": \"boolean\",\n \"description\": \"The entry is on screen now (the stack had nothing open).\"\n },\n \"position\": {\n \"type\": \"number\",\n \"description\": \"1-based place on the stack.\"\n },\n \"total\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot answer` sends on the runpane:autopilot:answer daemon channel.\",\n \"required\": [\n \"id\",\n \"answer\",\n \"because\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"answer\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"choice\"\n ],\n \"properties\": {\n \"choice\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"text\"\n ],\n \"properties\": {\n \"text\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"because\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A D-n, a document or the person's words.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot close` sends on the runpane:autopilot:close daemon channel.\",\n \"required\": [\n \"id\",\n \"reason\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot rewrite` sends on the runpane:autopilot:rewrite daemon channel. Link paths are absolute: runpane resolves a relative path against the caller's directory.\",\n \"required\": [\n \"id\",\n \"ui\",\n \"title\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"description\": \"The new item's id.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"pausedMark\": {\n \"type\": \"object\",\n \"required\": [\n \"at\",\n \"by\"\n ],\n \"properties\": {\n \"at\": {\n \"type\": \"string\",\n \"description\": \"When the tab was paused (ISO 8601).\"\n },\n \"by\": {\n \"enum\": [\n \"manual\",\n \"auto\"\n ],\n \"description\": \"manual: a person or agent paused it. auto: auto-pause paused it after idleHours without use.\"\n },\n \"idleHours\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"pauseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"action\",\n \"scope\",\n \"panelIds\",\n \"unchanged\",\n \"failed\",\n \"message\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when a tab could not be resumed; the others still were.\"\n },\n \"action\": {\n \"enum\": [\n \"paused\",\n \"resumed\"\n ]\n },\n \"scope\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"panel\",\n \"pane\",\n \"session\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Tabs this call paused or resumed.\"\n },\n \"chatTabsNotPaused\": {\n \"type\": \"number\",\n \"description\": \"On a pause: chat tabs in scope, which can't pause yet, so their agents keep running.\"\n },\n \"unchanged\": {\n \"type\": \"number\",\n \"description\": \"Terminal tabs in scope that were already paused (for pause) or not paused (for resume).\"\n },\n \"failed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"error\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, then route by the rule 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: if the work's branch already has a Pane (find it with `runpane panes list --repo <repo> --json` by matching the PR's head branch against `git -C <worktreePath> branch --show-current`), open a new agent tab in it with `runpane panels create --pane <id> --agent <agent> --initial-input \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`; only when the branch has no Pane yet, create one with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` (the new Pane is pinned by default; add `--no-pinned` for a throwaway Pane, add `--base-branch <ref>` to branch from something other than the repository default, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`. For long-lived supervision, use `runpane watch --follow` instead of polling wait or screen.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use `panes create` only when the work's branch has no Pane yet (new work, or an existing PR with no Pane), since each new Pane creates a worktree. Every later agent for that branch opens as a new tab in its Pane with `panels create --pane <id>`, never a new Pane. Tabs share one checkout: only the implementer changes the branch or HEAD, one writer at a time; review, audit and QA tabs never run `git checkout`, `reset` or `stash`.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: Pane's skills live in `<PANE_DIR>/skills/pane-chat/skills/`, and the Pane Chat entry point is `<PANE_DIR>/skills/pane-chat/pane-orchestrator/SKILL.md`; read the skill that matches the request, such as `discussion`, `create-ticket`, `tdd`, `prepare-pr`, or `review`.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; `panes create` pins the new Pane into the UI's favorite/pin set by default, so no follow-up `panes pin` call is needed and `--no-pinned` is the opt-out for throwaway Panes.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"Read and change a repo's Project Settings (build, run, and archive scripts, worktree folder, environment variables) with `runpane repos get` and `runpane repos update`; never edit Pane's database by hand.\",\n \"When a command needs a secret (an API key, a token), run it with `runpane secrets exec --project <project> -- <command>`. If the secret is missing, ask the person with `runpane secrets request`; never ask them to paste a secret into the chat, and never print one.\",\n \"If WSL cannot reach Pane, the user may be running Windows Pane; try `/mnt/c/Users/<you>/.pane/bin/runpane doctor --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude|cursor> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes, wait for each agent's input box, and verify its starting message in one call.\",\n \"Use `runpane panels screen` for compact current state, including Claude and Codex composer state. Use `panels wait` for create-time readiness or text checks. Use `runpane watch --follow` to block on workspace transitions (READY, BLOCKED, IDLE, STUCK, EXIT) without polling, or `runpane watch --until-event` to exit on the first one. Use `panels submit` to send and submit a new turn. Use `panels submit-composer --strategy auto` only for a composer that was filled separately.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"Pane terminals draw inline images: sixel, iTerm2 inline images, and the kitty graphics protocol. Tools that need kitty graphics, such as terminal-browser and terminal-doom, run inside a Pane panel; `runpane doctor --json` reports the exact list under `terminal.graphicsProtocols`.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen`. For a harness that consumes streaming output, use `runpane watch --follow` (with `--session <id>` for a Session); its named consumer applies settling and batching. Adding numeric --since leaves it unnamed and bypasses cadence; --as cannot be combined with --since. A harness that wakes only when a process exits uses `runpane watch --as <name> --until-event --json` and runs it again after each event. Single-event waits deliver immediately without follow cadence. Never pipe `--follow` into `grep -m1` or `head`.\",\n \"When several agents share one resource, such as a test account, name a lock in their prompts: each worker runs `runpane lock acquire --name <name> --ttl 30m --wait 1800000 --json` before using it and `runpane lock release --name <name>` after.\",\n \"Loops (`runpane loops create`) run a task on a cron schedule, each run in a new Pane or, with `--session`, as a message to a Session's agent. They are for large recurring jobs, such as a nightly bug sweep, and run at most once an hour. For a short wait, use your agent harness's own sleep and wake instead.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude|cursor>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--build-script <script>\",\n \"--run-script <script>\",\n \"--archive-script <script>\",\n \"--worktree-folder <path>\",\n \"--env <json>\",\n \"--env-remove <name,...>\",\n \"--name <name>\",\n \"--clear <setting,...>\",\n \"--from-json <path|->\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List Pane secrets: project, name, time and machine. Never a value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment.\",\n \"arguments\": [\n \"--project <project>\",\n \"--only <name,...>\",\n \"--env <VAR=NAME>\",\n \"--file <name>\",\n \"-- <command>...\"\n ]\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card; returns saved or declined, never the value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--name <name>\",\n \"--title <text>\",\n \"--reason <text>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--pinned\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--session <id|name>\",\n \"--merged\",\n \"--closed\",\n \"--source <user|agent>\",\n \"--force\",\n \"--remove-worktree\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--name <new-name>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally a panel) on explicit user request.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--panel <panel-id>\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open reviewer, QA, fix or helper agent tabs inside an existing Pane; they share that Pane's worktree. The default for any later agent on existing work.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Show the user an HTML page, plan, report, dev server URL, or file as a tab in split view beside the agent.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--url <url>\",\n \"--file <path>\",\n \"--title <title>\",\n \"--split\",\n \"--tab\",\n \"--no-focus\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened once it no longer matters; refuses the person's tabs, busy agents and unsent input.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--dry-run\",\n \"--force\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the key for its current state.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter|tab>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place (same panel id), resuming its agent's conversation.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--session <id|name>\",\n \"--all-stopped\",\n \"--concurrency <count>\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace events from the durable daemon journal; follow uses conservative cadence and quiet expiry by default.\",\n \"arguments\": [\n \"--follow\",\n \"--until-event\",\n \"--session <id|name>\",\n \"--self-test\",\n \"--kinds <kind,...>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question wherever they are and wait for the answer; prefer it to asking in the terminal.\",\n \"arguments\": [\n \"--ui <options|approve|ask|info>\",\n \"--title <text>\",\n \"--choices <json>\",\n \"--links <json>\",\n \"--resume <id>\",\n \"--json\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update Pane and the active supported CLI with durable status.\",\n \"details\": \"Defaults to both components. Use --pane-only or --cli-only to select one. Preflight checks installation ownership and supervisor capability before downtime.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Requested app and CLI release version.\"\n },\n {\n \"name\": \"--pane-only\",\n \"required\": false,\n \"description\": \"Update only the installed Pane app.\"\n },\n {\n \"name\": \"--cli-only\",\n \"required\": false,\n \"description\": \"Update only the invoked supported CLI installation.\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<operation-id>\",\n \"required\": false,\n \"description\": \"Read durable status without a running Pane.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Wait until the operation completes or requires attention.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Inspect preflight without staging or stopping Pane.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Requires standalone Node and a current-user OS supervisor. npm, managed CLI, and supported pip environments retain their actual ownership. System-package Linux and installations requiring elevation are refused before downtime.\",\n \"The operation preserves a pre-migration data snapshot; it never blindly rolls an old app or database over migrated state.\",\n \"The initial result can be accepted or staging; it is not a claim that the update or restoration completed.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Accepted and ignored, so one --pane-dir can be passed to every runpane command.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n },\n {\n \"name\": \"--report\",\n \"required\": false,\n \"description\": \"Prepare a redacted, inspectable diagnostic report.\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Report title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm creating exactly one greenfield-inc/Pane issue.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\",\n \"runpane doctor --report --title \\\"runpane watch failed\\\" --body-file ./watch.txt --json\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"daemon repair\": {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"details\": \"Use this only after doctor reports a stale or broken remote-daemon launcher. The command invokes the installed Pane executable in service-repair mode and does not rerun remote setup.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Remote daemon data directory; defaults to ~/.pane_remote.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Existing Pane executable used to perform repair.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip the service restart confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print one machine-readable repair result.\"\n }\n ],\n \"examples\": [\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"notes\": [\n \"Repair does not create pairing credentials, change tunnel configuration, or download Pane.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\",\n \"agentContextUnknownCommandError\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\",\n \"An unknown `--command` exits 2. With `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout, where `candidates` lists the closest command names first; without `--json` the same candidates go to stderr.\",\n \"`--pane-dir <path>` is accepted and ignored, so one `--pane-dir` can be passed to every runpane command.\"\n ]\n },\n \"mcp\": {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"details\": \"Use this to register Pane with an MCP client. Pane registers it with Claude Code, Codex, and Cursor automatically; each tool mirrors one runpane command and returns its --json result.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated toolsets to serve: core (default), all, read, repos, panes, panels, agents, sessions, loops, workspace, git, docs, links, admin.\"\n },\n {\n \"name\": \"--read-only\",\n \"required\": false,\n \"description\": \"Serve only read-only tools.\"\n }\n ],\n \"examples\": [\n \"claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \"codex mcp add pane -- npx --yes runpane@latest mcp\"\n ],\n \"notes\": [\n \"Only the npm package and the Pane app include the MCP server; the Python package prints how to run it with Node.\",\n \"Commands that need --yes in the CLI take `yes: true` as a tool input.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry with `/mnt/c/Users/<you>/.pane/bin/runpane repos list --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"repos get\": {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"details\": \"Use this before changing a repo's settings, or to find out what a new Pane will run. Each script reports `value` (saved in Project Settings), `fallback` (from the repo's config file), and `effective` (what Pane runs: the saved value when set, otherwise the fallback). `envVars` lists the project environment variables: plain values in full, keychain items and Pane secrets as references, never their values.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name. Defaults to active.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable settings.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos get --repo Pane --json\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"notes\": [\n \"Config-file fallbacks are read from the repository root. At run time Pane reads them from each Pane's worktree, so a branch that changes its config file can run something else.\",\n \"`currentBranch` is detected from the repository; it is not a setting.\"\n ]\n },\n \"repos update\": {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"details\": \"Saves through the same path as the Project Settings dialog: Pane drops its cached project context, rebuilds the run commands, and open windows reload the repo. Settings you do not pass keep their value. Clear a setting with `--clear` or an empty value; a cleared script falls back to the repo's config file. --env adds or replaces project environment variables by key, and --env-remove removes them. A variable is plain text, an existing macOS keychain item, or a Pane secret reference; to store a new secret value, use `runpane secrets request` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Repository display name. It cannot be cleared.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Project variables to add or replace by key: a JSON array of {key, source: \\\"plain\\\", value}, {key, source: \\\"keychain\\\", service, account} or {key, source: \\\"secret\\\", project, name}.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"required\": false,\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"JSON settings object with keys name, buildScript, runScript, archiveScript, worktreeFolder, openIdeCommand, systemPrompt, and envVars ({set: [...], remove: [...]}); null clears. Flags override it.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Return the settings as they would be after the update without saving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --yes --json\",\n \"runpane repos update --repo Pane --clear archive-script --dry-run --json\",\n \"runpane repos update --repo Pane --from-json settings.json --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"notes\": [\n \"`changed` lists the settings whose saved value differs from before.\",\n \"The worktree folder applies to Panes created after the update; existing worktrees stay where they are.\",\n \"Plain values are saved and shown in full; do not put a secret in a plain variable. Point the variable at a Pane secret instead.\"\n ]\n },\n \"repos rename\": {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"details\": \"Use this to replace an automatic repository name, such as one with a numeric suffix, with a clear one. It changes only the display name.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository to rename: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"New non-empty repository name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated repository.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos rename --repo 3 --name Lev --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"notes\": [\n \"The repository path, Panes, and settings are unchanged.\"\n ]\n },\n \"secrets list\": {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"details\": \"Use this to find the project and name to pass to `runpane secrets exec`. Each entry has project, name, updatedAt and machine. storage says how this machine keeps values; encrypted false means a plain 0600 file on a machine without an OS keyring.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": false,\n \"description\": \"Only this project's secrets.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets list --json\",\n \"runpane secrets list --project stripe --json\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"notes\": [\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets set\": {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"details\": \"The value comes only from stdin, so it never appears in argv or a transcript. One trailing newline (\\\\n or \\\\r\\\\n) is dropped; an empty value is refused. If stdin is a terminal it is refused too: pipe the value in, or have the person use Settings > Secrets or `runpane secrets request`. With --json it prints {ok, replaced, secret} (secretSetResult).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretSetRequest\",\n \"secretSetResult\"\n ],\n \"notes\": [\n \"Never set a value someone pasted into the chat; ask with `runpane secrets request` instead, so the value stays out of the transcript.\",\n \"There is no MCP tool for this command: MCP arguments land in the transcript.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets delete\": {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"details\": \"Deletes <project>/<name> here. Each other machine deletes it the next time a desktop or the phone syncs with it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"notes\": [\n \"A secret that does not exist fails with SECRET_NOT_FOUND.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets exec\": {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"details\": \"Reads the secrets from this machine's Pane when it runs, starts the command with your environment plus the secrets, passes stdin, stdout and stderr through, and exits with the command's exit code. By default it injects every secret in the project under its own name. --only limits that to the names given. --env VAR=NAME injects secret NAME as VAR. --file NAME writes secret NAME to a 0600 file in a private temp folder and sets NAME to the file's path; the file is removed when the command exits. Injected names override your environment.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Inject only these secrets, comma-separated.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME>\",\n \"required\": false,\n \"description\": \"Inject secret NAME as VAR. Repeatable.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Write secret NAME to a private temp file and set NAME to its path. Repeatable.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command to run and its arguments.\"\n }\n ],\n \"examples\": [\n \"runpane secrets exec --project stripe -- pnpm test:webhooks\",\n \"runpane secrets exec --project stripe --env STRIPE_API_KEY=STRIPE_SECRET_KEY -- node scripts/sync.js\",\n \"runpane secrets exec --project gcp --file SERVICE_ACCOUNT_JSON -- sh -c 'gcloud auth activate-service-account --key-file \\\"$SERVICE_ACCOUNT_JSON\\\"'\"\n ],\n \"notes\": [\n \"Target names PATH, WORKTREE_PATH and PANE_*, in any case, are refused with SECRET_RESERVED_NAME, and nothing runs.\",\n \"A missing secret fails with SECRET_NOT_FOUND before anything runs; ask for it with `runpane secrets request`.\",\n \"Do not echo a secret from inside the command: its output lands wherever the command's output goes, such as your transcript.\",\n \"There is no MCP tool for this command; run it in a shell.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\",\n \"Secrets are the machine owner's: every secrets command in a teammate's `runpane workspace exec` (PANE_TEAMMATE_LOGIN set) fails with SECRET_OWNER_REQUIRED.\"\n ]\n },\n \"secrets request\": {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"details\": \"Shows a Notify card with your title and reason, a masked field, Save securely and Decline. What the person types is saved on this machine as the Pane secret <project>/<name> and synced to their other machines. You get back {status: \\\"saved\\\", project, name} or {status: \\\"declined\\\"}, never the value. Then use it with `runpane secrets exec`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project, such as the service or repo it belongs to: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name, such as STRIPE_SECRET_KEY: letters, digits and underscores, not starting with a digit.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What the secret is, in a few words, such as \\\"Stripe test secret key\\\".\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What you need it for, in one sentence.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook integration tests against Stripe's test mode.\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"A result of {waiting: true, id} means the card is still open and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now. Do not ask for the secret in the chat; follow next.\",\n \"Saving replaces a secret that already exists under that name; the card says so.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\",\n \"`status` is `running` while a tool or agent terminal is live, otherwise the stored lifecycle value (`error` and `initializing` always show as stored). The Pane's dock shell (`dock: true` in `panels list`) does not count, so a Pane whose only live terminal is the dock reads its stored value, usually `stopped`. `agentState` is the most urgent state of the live agent panels: `blocked`, `working`, `ready`, or `none`. `agentStatus` (`active`/`idle`) is kept for older clients.\"\n ]\n },\n \"panes cost\": {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"details\": \"Use this to attribute estimated workspace token costs to Pane lifetimes and inspect the models and cache efficiency behind each Pane's spend.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Return only the selected Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes cost --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"notes\": [\n \"The report range is the last 30 days.\",\n \"Costs are estimates based on API token prices.\",\n \"The unattributed bucket reconciles the Pane rows against totals in the unscoped result.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use this only when the work's branch has no Pane yet: new work, or an existing PR with no Pane. Each new Pane creates a worktree that checks out, installs and often builds, so creating several at once can freeze the machine. First find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`. For review, follow-up review, fixes, QA or any other agent on work that already has a Pane, use `runpane panels create --pane <id>` to open a new agent tab in that Pane instead. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch the new worktree from this ref (for example origin/main or release/2.4) instead of the repository default.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree and branch name; defaults to --name. Characters outside [a-z0-9-] are removed.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"required\": false,\n \"description\": \"Ref to branch from, such as origin/main or release/foo. --base is an alias.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Exact new branch name, slashes included (for example agents/w5a). Fails if the branch exists; never renamed to be unique. Defaults to the worktree name.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree directory name; defaults to --name.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--pinned\",\n \"required\": false,\n \"description\": \"Accepted for backward compatibility and now a no-op; `panes create` already pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Create the pane unpinned instead of the default pinned (the Pane UI's favorite/pin star). Pinning never implies focus.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name w5a --base origin/main --branch agents/w5a --agent <agent> --prompt-file prompt.md --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent claude --initial-input-file brief.md --as-file-pointer --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: before creating one, find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`, and add a tab with `panels create --pane <id>` when it exists.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"`panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`: Pane creates the worktree on exactly that branch name. `--branch` is checked with `git check-ref-format --branch` and fails if the branch already exists; `--worktree-name` still names the directory.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved. `panes create` pins the new Pane by default, so a follow-up `panes pin` call is unnecessary; pass `--no-pinned` for throwaway shells or bulk imports. `--pinned` is still accepted and is now a no-op. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input. A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"With --wait-ready, `initialInput.delivery` reports `taken`, `queued`, `in-composer` or `unknown` with its evidence (`transcript`, `screen`, or `argv` for a prompt passed at launch); verifiedSubmitted is true for `taken` or `queued`. Routing initial input alone does not imply verified submission, and the create's `ok` is false unless a Claude or Codex starting message is verified.\",\n \"If initialInput.blocked.kind is submission_unverified, do not submit again automatically. Inspect initialInput.staged and attempts, then run nextCommand to resolve the ambiguous composer state.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, use Pane's own `runpane` (see doctor) and select the saved WSL repo by name or id, for example `runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json`.\",\n \"Use --wait-ready when an agent needs the started agent at its input box, and its starting message verified, instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"At most 2 Panes run setup at once (worktree file sync, dependency install, build script); more wait in line. A queued `panes create` returns the Pane id right away with `setupQueue` ({ position, message }) and no `panelId`; the agent starts when setup finishes. Find its panel later with `runpane panels list --pane <id> --json`.\",\n \"A Pane with its own worktree starts at the remote's current tip of a remote-tracking base (the default `origin/main`, or one named with `--base`): Pane fetches that branch first, waiting at most about 10 seconds, and creates started together share the fetch. Each item's `base` reports `{ ref, commit, refresh }`, where `refresh` is `fetched`, `recent` (reused a fetch from the last 30 seconds), `skipped` (a local branch or commit base) or `failed`. A failed refresh never fails the create; the Pane starts from the local ref and `base.notice` says what happened and how to catch up.\",\n \"`panes create --branch <name>` fails with code `ERR_RUNPANE_BRANCH_HAS_PANE` when another Pane already has that branch checked out. The message names the Pane and gives the next step: `runpane panels create --pane <id> --tool-command \\\"<cmd>\\\" --source agent --no-focus`.\"\n ]\n },\n \"panes adopt\": {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane.\",\n \"details\": \"Validates that the canonical path is a registered worktree of the selected saved repository, then creates a Pane record without creating, syncing, building, removing, or recreating the directory.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Saved repository selector.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<dir>\",\n \"required\": true,\n \"description\": \"Existing git worktree path.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Agent whose terminal to create; with --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom command to stage or launch instead of a built-in agent, such as a wrapper that runs one.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Persist this agent session id and stage its resume command.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Top-level sidebar folder.\"\n },\n {\n \"name\": \"--launch\",\n \"required\": false,\n \"description\": \"Run the agent (or its resume command) immediately.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sent once the launched agent is ready. Requires --launch.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Alias for --initial-input-file; read the prompt from a file or stdin. Requires --launch.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for the launched agent to be ready and the prompt submitted before returning. Requires --launch.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Do not pin the adopted pane.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the adopted pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview only.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent codex --resume <id> --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --tool-command \\\"agent-farm run free-range\\\" --agent claude --launch --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent claude --launch --prompt-file prompt.md --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"The resume command is staged without Enter unless --launch is passed.\",\n \"Archiving never deletes an adopted worktree unless `runpane panes archive --remove-worktree` is used.\",\n \"One of --agent or --tool-command is required.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"--resume needs a built-in agent command; a wrapper command resumes its own way.\",\n \"The adopted Pane reports `status: running` in `panes list` while its tool terminal is live.\",\n \"A prompt (--prompt, --initial-input, --prompt-file, --initial-input-file) or --wait-ready needs --launch; without it adopt fails instead of dropping the prompt.\",\n \"With --launch the result carries the same readiness, initialInput, and nextCommand fields as panes create. With --resume the prompt goes to the resumed conversation's composer once the agent is ready.\",\n \"To start new work on a named branch, prefer `panes create --base <ref> --branch <name>` over `git worktree add` plus adopt.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. The safety check refreshes the configured upstream before comparing it to HEAD and includes exact unpushed commit IDs and subjects. When the branch has no upstream or its upstream is gone (GitHub deletes merged branches), a merged pull request whose head is HEAD counts as pushed, so squash-merged branches archive without --force. Refuses unsafe archive operations unless --force is used. Use --dry-run to inspect the same evidence without mutating Pane state. Use --session <id|name> --merged to close out every finished Pane of a Session at once. Archive also refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless --force is used, since archiving stops the agent or loses the input. Add --closed to the Session sweep to also archive Panes whose PR closed without merging.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane/session id to archive. Required unless --session is used.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn or waiting for an answer (archiving stops it), or a tab holds unsent input (archiving loses it).\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"required\": false,\n \"description\": \"Also check and remove an adopted Pane's worktree. Pane-managed worktrees are always removed; the local branch is always kept.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Archive the Panes associated with this named Session instead of one Pane. Requires --merged.\"\n },\n {\n \"name\": \"--merged\",\n \"required\": false,\n \"description\": \"With --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work; skip the rest with a reason and next step. An open PR is skipped as pr-open, no PR or a closed unmerged PR as pr-not-merged, and a PR state GitHub cannot confirm as pr-status-unknown.\"\n },\n {\n \"name\": \"--closed\",\n \"required\": false,\n \"description\": \"With --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks. Without --closed they are skipped as pr-not-merged.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Refresh and print archive safety evidence without archiving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --dry-run --json\",\n \"runpane panes archive --pane <pane-id> --remove-worktree --yes --json\",\n \"runpane panes archive --session <id|name> --merged --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --closed --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --remove-worktree --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"Inspect safetyCheck.unpushedCommitDetails for exact commit IDs and subjects; upstreamRefreshed confirms the tracking ref was refreshed first.\",\n \"A --dry-run result sets dryRun:true and wouldArchive without deleting the Pane or its worktree.\",\n \"A successful archive reports worktreeCleanup `completed` once the worktree is gone from its path and from git. trashDeletion `pending` means a large worktree (for example one with node_modules) is still being deleted from .git/pane-trash in the background; that is still ok:true. `timeout` (also ok:true) means removal is still running; only `failed` sets ok:false.\",\n \"safetyCheck.mergedViaPr names the merged PR whose head is HEAD; it needs an authenticated `gh`. Without it, a squash-merged branch whose remote branch was deleted reports unpushed-commits.\",\n \"An adopted Pane (panes adopt) keeps its worktree unless --remove-worktree is passed. `panes restore` does not recreate a removed adopted worktree; the local branch is kept, so `git worktree add <path> <branch>` brings it back.\",\n \"With --session --merged, each item has outcome archived, would-archive (dry run), skipped (with skipped.code and skipped.message), or failed.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\",\n \"Undo an archive with `runpane panes restore --pane <pane-id> --yes`, which recreates the worktree. `runpane links create --pane <pane-id>` gives the user a link to review the Pane first.\",\n \"When the safety check is skipped, `safetyCheck.reason` says why: `external-worktree` (an adopted Pane whose worktree Pane does not own), `main-repo`, `missing-project-context`, or `git-error`. `worktreeWillRemain: true` means archiving leaves the worktree on disk.\",\n \"An agent mid-turn or waiting for an answer blocks archive with `agent-busy` (`safetyCheck.busyAgents` lists them); wait until it is idle with `runpane panels wait --panel <id> --for idle`, then archive again. The Session sweep skips such a Pane with skipped.code `agent-busy`.\",\n \"A tab holding text typed into an agent's composer but not sent, or an editor with unsaved edits, blocks archive with `unsent-input` (`safetyCheck.unsentInput` lists them), the same input `panels close` refuses to lose; send, save or clear it, then archive again. `--force` discards it.\"\n ]\n },\n \"panes pin\": {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to pin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pin --pane <pane-id> --yes --json\",\n \"runpane panes pin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.\",\n \"Pinning does not focus the Pane.\"\n ]\n },\n \"panes unpin\": {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to unpin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes unpin --pane <pane-id> --yes --json\",\n \"runpane panes unpin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.\",\n \"Unpinning does not focus the Pane.\"\n ]\n },\n \"panes pause\": {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"details\": \"Pauses every terminal tab of a Pane, run scripts included, until `panes resume` resumes it. A chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`. A Pane reads as paused (`panes list` `paused`) while all of its terminal tabs are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pause --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Find idle Panes with `panes list --json`: `inactiveSince` is the latest terminal output or input in its tabs, or the last time the person selected it.\",\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panes resume\": {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"details\": \"Resumes a Pane's paused tabs. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes resume --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"panes rename\": {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"details\": \"Trims and sets the Pane display name through the same persistence and session-updated event path used by the UI. Dry-run returns the projected updated pane without mutating.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to rename.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<new-name>\",\n \"required\": true,\n \"description\": \"New non-empty Pane/session name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and return the projected updated pane without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated pane.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"notes\": [\n \"Empty and whitespace-only names are rejected; no additional maximum length is imposed beyond the existing UI and database behavior.\",\n \"The JSON result includes the updated pane summary so callers do not need a second panes list request.\"\n ]\n },\n \"panes focus\": {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels).\",\n \"details\": \"Brings the Pane desktop window to the foreground and selects the requested Pane exactly like clicking it in the sidebar; when --panel is given it also selects that panel/tab inside the Pane. This steals window focus, so it must only be run on an explicit user request to open, focus, or show a Pane, never proactively. Focusing does not change the Pane's worktree, branch, panels, or pinned state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to focus. Must be an active (non-archived) Pane.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Optional panel/tab id inside the Pane to select; must belong to the focused Pane.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change focus behavior.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands; required in non-interactive contexts because focusing steals window focus.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes focus --pane <pane-id> --source user --yes --json\",\n \"runpane panes focus --pane <pane-id> --panel <panel-id> --source user --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"notes\": [\n \"Run this only when the user explicitly asks to open, focus, show, or switch to a Pane; never focus a Pane proactively because it steals the user's window focus.\",\n \"Archived Panes cannot be focused; the command fails clearly instead of falling back to focusing another Pane.\",\n \"If --panel does not belong to the focused Pane, the command fails clearly rather than selecting a different panel.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\",\n \"`agentType` is the agent Pane treats the panel as; `agentDetection` says how it knows (`declared`, `command`, `process`, or `screen`), and `launchCommand` is the command the panel was launched with.\",\n \"`resumeId` is the agent session ID Pane saved for reopening the panel, and `resumeSource` says which rule resumes with it: `agent-farm` (an `agent-farm run` or `agent-farm agent` command), `configured` (the panel's own resume command), or `agent` (a built-in agent's captured session). Both are omitted when no ID is saved.\",\n \"Every Pane has one plain shell titled `Terminal`, its terminal dock: `dock: true` marks it, and the field is omitted on every other panel. Setup progress notices appear there; it is not a tool or agent tab and does not count toward the Pane's `status`. A `--tool-command` tab without `--title` or `--agent` is titled from its command (whole words up to 40 characters).\",\n \"`openedBy` says who opened the tab through runpane: `source` (`agent` or `user`) and, when known, `panelId` of the tab that opened it. It is omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose `openedBy.source` is `agent`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters. A chat tab has no terminal: input fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated key names to press in order: enter, escape, tab, backspace, space, up, down, left, right, ctrl-c, ctrl-d, or any single character.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\",\n \"runpane panels input --panel <panel-id> --keys down,enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"To answer a menu or prompt, prefer `--keys` (for example `--keys down,enter`) over escape sequences in `--text`.\",\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. On a chat tab it returns the newest transcript lines with source acpTranscript. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"The composer object reports whether a Claude or Codex composer is present and whether it holds undelivered text. Placeholder and suggestion text (drawn dim or grey, such as Claude's suggested next prompt) does not count as undelivered text; `composer.ghostText` carries it, and text output marks its line with `⟨suggestion⟩`.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"details\": \"Use this for ordinary interactive submissions. For Claude, Pane waits for the composer to appear and show the staged text, then sends Enter on its own and verifies that the composer cleared; Claude keeps an Enter that arrives together with the text as a newline. For a Codex composer, Pane stages the text, waits for paste handling, then queues with Tab while working or submits with Enter when ready, and verifies delivery when visible evidence is available. Wrapper-launched agents are handled the same way once Pane knows the agent (declared, from the foreground process, or from the screen). A Claude or Codex panel with no composer on screen gets nothing: the result is `ok: false` with `blocked.kind: composer-unknown`. Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working. Other terminals receive a normalized CR Enter. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--interrupt\",\n \"required\": false,\n \"description\": \"Interrupt the current turn and wait up to 30 seconds for an empty idle composer before delivering. Escape for Codex/Claude; Ctrl+C for Cursor.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --input-file prompt.md --as-file-pointer --wait --yes\",\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\",\n \"runpane panels submit --panel <panel-id> --input-file brief.md --as-file-pointer --yes --json\",\n \"runpane panels submit --panel <panel-id> --text \\\"Stop that approach and follow this correction\\\" --interrupt --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes sequenceName, verifiedSubmitted, delivery and nextCommand. For Claude and Codex, `delivery.state` is `taken` (the agent started a turn with the text), `queued` (it holds the text until its current turn ends), `in-composer` (the text is still in the composer) or `unknown`, and `delivery.evidence` is `transcript` (the agent's own transcript) or `screen`. verifiedSubmitted is true for `taken` or `queued`; never resend those. If ok is false, inspect blocked and do not assume the turn started.\",\n \"Do not follow `panels submit` with `panels submit-composer`; Claude and Codex composer submission is handled atomically.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\",\n \"On `blocked.kind: composer-unknown`, nothing was typed. Check `panels screen`; if the agent is at a prompt Pane does not recognise, use `panels input`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"With --interrupt, Pane sends the stop key only while the agent is active, waits for an empty idle composer, then submits. If readiness times out, replacement text is not sent. A queued result is not read yet: delivery.message explains the delay; never resend queued text. Interrupt success requires delivery.state: taken. Unknown agents and occupied composers are rejected without typing.\",\n \"On a chat tab, submit sends an ACP prompt and returns sequenceName=acp, enter=none, operationId and acp-receipt delivery evidence; a prompt sent while the agent works is queued and sent next. Do not blindly resend after a lost response.\"\n ]\n },\n \"panels resume\": {\n \"name\": \"panels resume\",\n \"summary\": \"Restart stopped terminal panels in place, resuming their agents' conversations: one panel, a Session's panels, or every stopped agent panel.\",\n \"details\": \"Use this when `panels list`, `panels screen`, or `panels wait` report `running: false`, or `watch` reports `panel.stopped`, typically after a Pane restart or once its agent exited back to the shell. The panel keeps its id. Pane re-runs the panel's saved launch command resolved to the agent's resume command (Claude `--resume <session>`, `codex resume <session>`, Cursor `--resume`), the same restart the app runs when it shows the panel. It never takes focus. A panel whose agent exited back to its shell has its saved command run again in that shell. On a running panel it does nothing and returns `action: \\\"already-running\\\"`. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope (plain shells and paused tabs are left alone), `--concurrency` at a time, skips running panels, and returns one result per panel; one failure does not stop the others.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Terminal panel id. Give exactly one of --panel, --session, or --all-stopped.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of this Session's associated Panes.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of every non-archived Pane.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10. With --wait-ready the resumed panels then wait together, so the call takes the restarts plus one --ready-timeout-ms.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the resumed agent is ready, with the same startup-screen checks as `panels create --wait-ready`.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"notes\": [\n \"If readiness.blocked is present, the agent is at a startup prompt (trust, update, [y/n]); inspect `panels screen` or follow blocked.suggestedCommand. Ask the user before answering trust or permission prompts.\",\n \"A Claude panel resumes the conversation whose session id Pane launched it with; a Codex panel resumes the session id Pane captured from its output. Without one, the panel relaunches its original command.\",\n \"A bulk resume is ok only when every panel resumed and, with --wait-ready, became ready. Check items[].readiness.blocked and items[].error per panel.\"\n ]\n },\n \"panels close\": {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"details\": \"Closes a tab through the same path as closing it in Pane, so a terminal tab's process stops. It never archives the Pane or touches its worktree. Only a tab an agent opened can close: Pane records the opener when a tab is opened with `--source agent` (panels open, panels create, panes create, agents start). It refuses, without closing, a tab not opened by an agent (the person's, and every tab from before Pane recorded openers), a permanent tab, an agent mid-turn or waiting for an answer, unsent composer text or unsaved editor edits, and a live tab whose input Pane cannot read (agents other than Claude and Codex, and chat tabs). A refusal exits 1 with `refused.code`, `refused.message` and `refused.next`. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports the same without closing. `--force` (only for a stuck tab, or one the person asked you to close) closes it anyway, as the tab's close button does: a busy agent's turn and unsent input are lost, and the result says `forced: true`. A permanent tab still stays.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tab to close.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Close it even if an agent didn't open it, its agent is busy, or it holds unsent input. Only for a stuck tab, or one the person asked you to close.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels close --panel <panel-id> --dry-run --json\",\n \"runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"notes\": [\n \"ok:false with `refused` means the tab was NOT closed. `refused.code` is opened-by-user, permanent, agent-busy, unsent-input, or input-unknown; follow `refused.next`. Use --force only when the tab is stuck or the person asked you to close it.\",\n \"`alreadyClosed: true` (ok:true, closed:false) means no tab has that id any more.\",\n \"With --dry-run, `wouldClose` says whether the close would go ahead and `refused` says why not. Nothing closes; a refused dry run also exits 1.\",\n \"Open tabs with `--source agent` so they count as yours; `panels list` shows who opened each tab as `openedBy`.\"\n ]\n },\n \"panels pause\": {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"details\": \"Pauses one terminal tab until `panels resume --panel <panel-id>`, or its Pane or Session, resumes it. A chat tab can't pause yet: pausing one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE` and changes nothing. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels pause --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command. On a chat tab, a text wait (`--for text`) searches only what the agent writes after the wait starts, plus a reply still streaming then, never the person's prompts.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"If stopped is true, the panel's terminal is not running (for example after a Pane restart); run nextCommand (`panels resume`) instead of waiting again.\",\n \"The default timeout and screen are intentionally small for agent context safety.\",\n \"On a chat tab, status comes from the host ACP session (a turn that ended is ready, a pending approval or question is blocked) and the screen field has source=acpTranscript: transcript text, not a terminal screen.\"\n ]\n },\n \"panels last-message\": {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"details\": \"Use this when a worker is READY and you need what it said, such as its PR number or result. It reads the Claude or Codex transcript Pane found for the panel and never falls back to the screen.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Agent panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum characters to return; defaults to 20000. The end of the reply is kept.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels last-message --panel <panel-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"notes\": [\n \"Without a transcript (a shell panel, another agent, or no reply yet) the result is `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and the command exits 1. Use `panels screen` then.\",\n \"A worker that ran `runpane report` already handed back its state, PR, head, and summary; read those with `agents status` before reading the whole reply.\"\n ]\n },\n \"panels conversation\": {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"details\": \"Pane calls this for you: the Claude Code SessionStart hook that Agent Farm and Pane's plain claude tabs install runs `runpane panels conversation --hook` on start, /resume, /clear and compact. Run it yourself only to tell Pane a panel is on a conversation it could not learn from the hook.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Conversation (agent session) id the panel is on.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<startup|resume|clear|compact>\",\n \"required\": false,\n \"description\": \"What started the conversation.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id; defaults to PANE_PANEL_ID inside a Pane terminal.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id, with --panel; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels conversation --conversation <id> --source resume --json\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"notes\": [\n \"`sure` in the result is true when the report came since the panel's agent started; a Switch to chat or restart opens a sure conversation, and otherwise opens the folder's most recent one.\",\n \"`--hook` reads the hook's stdin, prints nothing and always exits 0; it is not offered over MCP.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent tab (reviewer, auditor, QA, fix or helper) inside an existing Pane, sharing its worktree. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: use this, not panes create, for any later agent on existing work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. This is the default way to start any later agent on work that already has a Pane, including reviewers, follow-up reviewers, auditors, fix implementers, QA and clean-context helpers. The new tab is a fresh agent that shares the Pane's worktree and branch; it does not create a new worktree. Review, audit and QA tabs never run `git checkout`, `reset` or `stash`, and a fix tab starts only after the previous writer has stopped. Use `panes create` only when the work's branch has no Pane yet. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--chat\",\n \"required\": false,\n \"description\": \"Open --agent claude or codex as a chat tab (ACP, no terminal) instead of a terminal tab. Needs chat tabs on in Settings > Advanced.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"required\": false,\n \"description\": \"Start this Agent Farm profile in a chat tab; Agent Farm prints its ACP launch (agent-farm run --print-acp).\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"required\": false,\n \"description\": \"A launch argument for --agent-farm; repeatable.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command \\\"agent-farm run free-range\\\" --agent claude --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"`--chat` or `--agent-farm` creates a chat tab: --initial-input is its first prompt, sent once when the conversation starts; --wait-ready waits for the agent to start. `panels submit`, `wait`, `screen`, `output`, `last-message`, `report` and `resume` work on chat tabs; `panels input` and `submit-composer` do not.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels open\": {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"details\": \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open the tab in; defaults to PANE_SESSION_ID (the calling panel's Pane, including a Session orchestrator's).\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"http(s) or file URL to open in a browser tab. WSL file URLs use the same automatic path conversion as --file. Pass exactly one of --url or --file.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"File inside the Pane worktree, relative or absolute. WSL panes accept Linux, Windows drive, and WSL UNC paths without manual conversion. Files open in an editor tab; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Tab title override.\"\n },\n {\n \"name\": \"--split\",\n \"required\": false,\n \"description\": \"Open in split view beside the primary tab group (default).\"\n },\n {\n \"name\": \"--tab\",\n \"required\": false,\n \"description\": \"Open in the current tab group instead of split view.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Open the tab without selecting it.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Select the tab (default). Never raises or focuses the Pane window.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels open --file plan.html --source agent --yes --json\",\n \"runpane panels open --url http://localhost:3000 --source agent --yes --json\",\n \"runpane panels open --pane <pane-id> --file src/app.ts --tab --source agent --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"notes\": [\n \"Prefer this over opening the system browser when showing the user a page, plan, report, or dev server.\",\n \"Write plans and reports as self-contained HTML files. Use --url with a file URL to render a page in a live browser, or --file to open its editable source; reopening the same target reuses its tab.\",\n \"When the user views Pane from another computer, a file page loads only files in its own folder and the folders inside it. Keep the page's CSS, scripts and images there, put them inline, or serve the folder over HTTP and open its localhost URL. Give file URLs as the host sees them, not as the other computer would.\",\n \"Opening a tab does not steal window focus; use `panes focus` only on explicit user request.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer. Auto uses Tab to queue while Codex is working and Enter when ready; Pane verifies that the text left the composer. A chat tab has no composer to submit: it fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName, verifiedSubmitted and, for Claude and Codex, delivery (`taken`, `queued`, `in-composer` or `unknown`, with `transcript` or `screen` evidence). If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\",\n \"With `--strategy auto`, if the staged text is still visible in the composer after the first attempt, Pane retries once and checks again: Tab for working Codex, Enter otherwise. It never retries an empty composer.\"\n ]\n },\n \"workspace state\": {\n \"name\": \"workspace state\",\n \"summary\": \"Read one snapshot of every Pane and CLI panel.\",\n \"details\": \"Use this instead of spawning one command per pane or panel.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optionally limit the snapshot to one saved repository.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane workspace state --json\"\n ],\n \"notes\": [\n \"The response carries the daemon epoch and current journal generation.\"\n ]\n },\n \"watch\": {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace transitions using the daemon journal.\",\n \"details\": \"`runpane watch --follow` is the monitor for an agent: with no other flags it prints WATCH OK once, then one line per event (reports, ready, blocked, idle, exits, Session membership, and PR conflicts, checks, and merges), batches non-urgent lines, and ends itself with WATCH EXPIRED after 3 hours with nothing happening. Set individual duration flags to override these defaults. `--until-event` is for harnesses that wake an agent when a process exits (Claude Code background Bash, Codex): it prints the first event worth waking for immediately, without follow settling or batching, and exits 0, and its named cursor means nothing is missed between runs. Every default is listed in the contract under defaults.watch; each override flag replaces one value.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--follow\",\n \"required\": false,\n \"description\": \"Keep watching, with the defaults in defaults.watch.follow: WATCH OK once, then one line per event; READY after 3m of quiet, BLOCKED after 30s, other lines batched at most every 10m (REPORT, BLOCKED, PR conflicts, and failed checks arrive at once); IDLE reminders at 10m, 30m, 1h, 3h, then daily; WATCH EXPIRED and exit 0 after 3h with nothing but IDLE.\"\n },\n {\n \"name\": \"--until-event\",\n \"required\": false,\n \"description\": \"Wait for the first event in defaults.watch.untilEvent.kinds (agent.report, agent.blocked, panel.exited, pane.gone), print that one line, and exit 0. Inside a Pane terminal the cursor defaults to until-<panel id>, so a re-run resumes where the last stopped. A RESET and the caller's own panel never end the wait. With --timeout-ms, exit 124 with WATCH TIMEOUT when the deadline passes. Cannot be combined with --follow or --self-test.\"\n },\n {\n \"name\": \"--session\",\n \"required\": false,\n \"value\": \"<id|name>\",\n \"description\": \"Follow every Pane associated with a named Session (id or exact name). The daemon re-reads membership on every read, so a Pane associated after arming is included and a detached or archived Pane drops out without a re-arm. The Session's own orchestrator panels are never reported. Cannot be combined with --pane or --all-managed; an unknown Session fails.\"\n },\n {\n \"name\": \"--pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Limit to a Pane; repeatable. Default all managed panes. Cannot be combined with --session.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane; repeatable.\"\n },\n {\n \"name\": \"--repo\",\n \"required\": false,\n \"value\": \"<selector>\",\n \"description\": \"Limit to a saved repository (active, id, path, or name). Default all repositories.\"\n },\n {\n \"name\": \"--name-contains\",\n \"required\": false,\n \"value\": \"<text>\",\n \"description\": \"Limit by Pane name substring.\"\n },\n {\n \"name\": \"--include-shells\",\n \"required\": false,\n \"description\": \"Include plain shell panels. Default: agent panels only. A report (agent.report) arrives from any panel either way.\"\n },\n {\n \"name\": \"--kinds\",\n \"required\": false,\n \"value\": \"<kind,...>\",\n \"description\": \"Replace the default event kinds: agent.ready, agent.busy, agent.blocked, agent.unknown, agent.idle, pane.created, pane.gone, panel.exited, panel.stopped, panel.paused, panel.unpaused, agent.report, pane.associated, pane.detached, pr.conflicted, pr.checks, pr.merged. Opt-in only, never in a default: agent.report.missing, and Autopilot's item.created, item.answered, session.created, session.archived, orchestrator.turn_ended. Defaults: defaults.watch.follow.kinds under --follow, defaults.watch.untilEvent.kinds under --until-event, every kind for a one-shot read.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"One JSON object per line. Control lines use kinds _ok, _heartbeat, _reconnected, _error, _reset, _dropped, _timeout, and _expired.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"required\": false,\n \"description\": \"Name the cursor (at-least-once delivery): 1-128 letters, numbers, dots, underscores, or hyphens. Default under --follow: session-<uuid> with --session, else this panel (PANE_PANEL_ID, shortened to panel-<sha256 prefix> when longer than 64 characters); under --until-event: until-<panel id>; otherwise anonymous.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"required\": false,\n \"description\": \"Use an explicit generation cursor instead of a name. Cannot be combined with --as or --follow.\"\n },\n {\n \"name\": \"--from\",\n \"required\": false,\n \"value\": \"<now|earliest>\",\n \"description\": \"Starting point for a new named cursor. Default now (silent baseline); earliest replays the journal.\"\n },\n {\n \"name\": \"--self-test\",\n \"required\": false,\n \"description\": \"Check the daemon watch path without moving any cursor, print WATCH OK, and exit 0.\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long a panel must stay quiet before READY. Default 3m. A BUSY, BLOCKED, UNKNOWN, EXIT, or GONE inside the window cancels it. 0 turns it off.\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long before BLOCKED is printed; an in-pane answer inside it cancels the line. Default 30s. 0 turns it off.\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: batch READY, IDLE, NEW, GONE, EXIT, and UNKNOWN lines at most once per interval; REPORT, BLOCKED, pr.conflicted, pr.base_changed, pr.base_deleted, and failed pr.checks arrive at once and carry the held lines. Default 10m. 0 turns batching off.\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: the first IDLE reminder after a READY panel stays idle. Default 10m. Later reminders back off to 30m, 1h, 3h, then daily. 0 turns IDLE off.\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: end the watch with WATCH EXPIRED and exit 0 after this long with no events but IDLE; any other event restarts the clock. Default 3h. 0 never expires.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: print a HEARTBEAT line this often as proof of life. Default off; a monitor that wakes an agent should leave it off.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"required\": false,\n \"value\": \"<duration>\",\n \"description\": \"Per-request wait in milliseconds; 0 reads once without waiting. Default 60000, capped at 120000 (the follow loop re-issues requests). With --until-event it is the deadline for the whole wait (exit 124 with WATCH TIMEOUT). Accepts ms/s/m/h units; bare values are milliseconds. 0/off is a nonblocking read only, and is rejected with --follow or --until-event.\"\n },\n {\n \"name\": \"--limit\",\n \"required\": false,\n \"value\": \"<count>\",\n \"description\": \"Maximum entries per response. Default 256.\"\n },\n {\n \"name\": \"--ack-now\",\n \"required\": false,\n \"description\": \"At-most-once named-cursor delivery (advance before you act). Default at-least-once.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"required\": false,\n \"description\": \"Include up to 120 characters of unsubmitted composer text (typed text only; prompt suggestions and placeholders excluded) in JSON entries. Default off: under --follow, JSON entries carry only heldInputPresent: true.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"required\": false,\n \"description\": \"Disable redacted held-input detection under --follow: no STUCK lines, and no heldInputPresent in JSON.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\",\n \"required\": false\n }\n ],\n \"examples\": [\n \"runpane watch --self-test\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane watch --follow --json --kinds agent.report,agent.blocked\"\n ],\n \"notes\": [\n \"Every default is in the contract under defaults.watch: follow (what --follow does) and untilEvent (what --until-event waits for). An override flag replaces one value; nothing else needs remembering.\",\n \"Arming: wait for WATCH OK before relying on a --follow monitor. It prints once when the watch is armed. Run the monitor under your harness's background monitor (one line = one notification).\",\n \"Ending: WATCH EXPIRED (exit 0) means nothing happened for --expire-after: say so, and arm a new watch with the next dispatch. A non-zero exit or a WATCH ERROR line means it failed: tell the user and re-arm once; if it dies again, file runpane doctor --report. If the message says the cursor was superseded, keep the replacement monitor and leave the old one stopped. Judge a watch by these lines, never by silence.\",\n \"--until-event prints exactly one line: the event (exit 0), WATCH TIMEOUT (exit 124, only with --timeout-ms), or WATCH ERROR (exit 2).\",\n \"STUCK means real unsubmitted composer text. An agent prompt suggestion (for example a grey Try \\\"...\\\" hint) never counts.\",\n \"Cadence state is held per named consumer. Without numeric --since, follow uses an explicit --as or assigns a Session, panel, or follow-<pid> name. With numeric --since it has no name and bypasses cadence; --as cannot be combined with --since. Held lines survive a reconnect of the same consumer; a changed filter re-delivers them under the new filter.\",\n \"Journal loss is surfaced through reset and dropped metadata.\",\n \"The daemon treats omitted idleAfterMs as disabled so older clients never receive agent.idle unexpectedly.\",\n \"Control lines are not journal entries. Lines text and JSON kind: WATCH OK (_ok, once when armed), HEARTBEAT (_heartbeat, only with --heartbeat), WATCH RECONNECTED (_reconnected, after a retried daemon error), WATCH ERROR (_error; exit 2 unless --follow or --until-event retries it), RESET (_reset, the journal restarted or the cursor fell behind; a fresh baseline follows), DROPPED (_dropped, entries lost to journal truncation), WATCH TIMEOUT (_timeout, --until-event deadline; exit 124), and WATCH EXPIRED (_expired, --expire-after passed; exit 0). Under --until-event, only _timeout and _error print.\",\n \"Under --follow, JSON entries for panels holding unsubmitted composer text carry heldInputPresent: true, the JSON equivalent of the STUCK line. Only typed composer text counts, the same reading as panels screen composer.hasUndeliveredText; an agent's dim or grey prompt suggestion or placeholder does not. --no-held-input turns this off.\",\n \"--session <id|name> scopes the watch to a named Session. The daemon resolves the Session once per request and its associated Panes on every read, so sessions associate and sessions detach take effect without a re-arm; archived and detached Panes drop out, and the Session's own orchestrator panels are excluded. Cadence state is keyed by the Session, not its current Panes, so membership changes keep held lines. A detached Pane's held lines are dropped.\",\n \"Under --session, pane.associated (JOINED <pane-name> pane <pane-id> session <session-id>) and pane.detached (LEFT ...) report membership changes; list them in --kinds next to the agent kinds. Other consumers receive them only when --kinds lists them, so older clients never see an unknown kind.\",\n \"PR watching: the daemon batches up to 25 direct GraphQL fingerprints per host every ~2 minutes. Full details are read on change or a 30-minute backstop. Existing Session branch discovery remains; standalone Panes register with pr watch --url <PR URL> [--head <full-sha>]. Immediate failures, required-check-aware success, head/review/comment/closed/merged events and GitHub outage/recovery events share durable baselines and the journal. A head pin prevents stale success, empty/partial checks never pass, and merge status BLOCKED/UNKNOWN conservatively withholds success. Rate limits honor server waits and reserve 10% for interactive use. Eight other errors or ten comment-only updates stop the watch with pr.stopped. See pr watch/unwatch/list.\",\n \"A daemon that predates --session returns no session field; runpane then fails with WATCH ERROR instead of watching every Pane.\",\n \"Replay: after a reset (RESET/_reset), the baseline restates current state. In JSON, those entries carry replay: true (and changedWhileAway: true after an epoch change). A replayed agent.ready is never READY: re-read runpane sessions overview instead of acting on it. Lines mode prints only CHANGED for them.\",\n \"agent.report (REPORT <pane-name> pane <pane-id> panel <panel-id> <state> [pr#<n>] [<head>][: <question>]) is a worker's `runpane report`. It is in the default kinds of --follow and --until-event, and arrives from any panel: a shell tab or a wrapper launch whose agent is not yet detected included. It bypasses --min-interval, and orchestrators treat it as the completion signal; a READY without a report means look, and maybe nudge.\",\n \"Never pipe --follow into grep -m1, head, or any other exit-on-match reader: it does not exit when its reader does until its next write. To wait for one event, use --until-event.\",\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"--follow has one default cadence: READY 3m, BLOCKED 30s, non-urgent batches 10m, IDLE after 10m with backoff, expiry 3h, heartbeats off. Individual duration flags override it. --present and --profile were removed; use --ready-settle, --blocked-settle, and --min-interval for timing overrides.\",\n \"--until-event delivers matching events immediately without follow settling or batching. A harness that consumes a named follow stream gets its cadence; a process-exit waiter keeps immediate single-event semantics. --follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"Autopilot's kinds reach only watches that list them in --kinds (a Session scope alone does not): item.created and item.answered when an approval or question starts or stops waiting on the person (ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>; JSON itemId and reason), session.created and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>; paneId is the Session's own Pane), and orchestrator.turn_ended when a Session orchestrator goes from working to idle (TURN ENDED ...). Autopilot waits with `runpane watch --as autopilot --kinds item.created,item.answered,session.created,session.archived,orchestrator.turn_ended --until-event --json`.\"\n ]\n },\n \"panes git-status\": {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"details\": \"Use this to check whether a Pane has work to commit, push, or rebase before archiving it or opening a PR.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes git-status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes commit\": {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"details\": \"Use this to commit a Pane's work the way the app's commit action does. The message needs a title line.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes push\": {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"details\": \"Use this to publish a Pane's commits, the way the app's push action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes push --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes pull\": {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"details\": \"Use this to bring remote commits into a Pane, the way the app's pull action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pull --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes rebase-main\": {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"details\": \"Use this to bring main's changes into a Pane. It rewrites the Pane branch history; conflicts are reported, not resolved.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rebase-main --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes restore\": {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"details\": \"Use this to undo `panes archive`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes restore --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes squash-rebase\": {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"details\": \"Use this to land a Pane's work on the repository's main branch the way the app's squash-and-rebase action does. It rewrites the Pane branch and updates main.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash\": {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"details\": \"Use this to set aside work in progress, the way the app's stash action does. `panes stash-pop` brings it back.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash-pop\": {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"details\": \"Use this to bring back work set aside with `panes stash`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash-pop --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes soft-reset\": {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"details\": \"Use this to reword or regroup the latest commit, the way the app's soft reset action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes soft-reset --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes fetch\": {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"details\": \"Use this to refresh remote branches before checking git status or rebasing.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes fetch --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes run-script\": {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"details\": \"Use this to start the dev server or app for a Pane, the way the app's Run button does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes run-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes stop-script\": {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"details\": \"Use this to stop what `panes run-script` started.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stop-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes move\": {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"details\": \"Use this to organize Panes. Folder ids come from `runpane folders list`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<folder-id>\",\n \"required\": true,\n \"description\": \"Folder id from folders list.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"folders list\": {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"details\": \"Use this to find folder ids for `panes move`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders list --repo <repo-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"folders create\": {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"details\": \"Use this to add a folder for grouping Panes.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Folder name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"links create\": {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"details\": \"Use this to hand the user a clickable link. It only builds the URL; opening it navigates and never changes Pane state.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel inside the Pane to select.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": false,\n \"description\": \"Repository id from repos list.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<session-id>\",\n \"required\": false,\n \"description\": \"Session id or name from sessions list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane links create --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"notes\": [\n \"Links look like `pane://open?pane=<pane-id>&panel=<panel-id>`. Give exactly one of --pane, --repo, or --session.\"\n ]\n },\n \"links open\": {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"details\": \"Use this to navigate the app to a Pane, panel, repository, or Session. It raises the Pane window, so run it only when the user asked.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--url\",\n \"value\": \"<pane-url>\",\n \"required\": true,\n \"description\": \"A pane://open link from links create.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane links open --url \\\"pane://open?pane=<pane-id>\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Opening a link never changes Pane state; it selects what the link names.\"\n ]\n },\n \"docs search\": {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"details\": \"Use this before guessing how Pane works. Results are short excerpts with a path; read the full text with `runpane docs read`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"Words to search for.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum results; defaults to 5.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs search --query \\\"archive a pane\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"notes\": [\n \"Works offline. Skills are found in the Pane data directory when the Pane app has installed them.\"\n ]\n },\n \"docs read\": {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"details\": \"Use this with a path from `runpane docs search`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Path from docs search, such as docs/PANE_MCP.md.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs read --doc docs/PANE_MCP.md --json\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"notes\": [\n \"Paths come from docs search: `docs/<file>.md`, `skills/<name>/SKILL.md`, `help/<topic>`, or `command/<name>`.\"\n ]\n },\n \"agents start\": {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. For work that already has a Pane, pass --pane <id>: the agent opens as a new tab sharing that Pane's worktree and branch. Only when the work's branch has no Pane yet, pass --repo and --name to create one. It returns the pane and panel ids and a pane:// link to hand the user.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Existing Pane to add the agent to as a new tab. Use this for any work that already has a Pane.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Name for the new Pane. With --repo, only for work whose branch has no Pane yet.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<task>\",\n \"required\": true,\n \"description\": \"The task to send once the agent is ready.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch to start the worktree from.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"notes\": [\n \"Creates the Pane in the background (no focus change), pinned, and waits for the agent to be ready before sending the prompt.\",\n \"Check on it with `runpane agents status`; send follow-ups with `runpane agents send`.\"\n ]\n },\n \"agents status\": {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"details\": \"Use this to see how an agent started with `agents start` is doing, or whether it needs input.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Screen lines to return; defaults to 40.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"notes\": [\n \"`status` is `working`, `ready`, `blocked`, `idle`, `exited`, or `unknown`. `blocked` means the agent is waiting on a person.\"\n ]\n },\n \"agents send\": {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"details\": \"Use this to give an agent its next instruction. To answer a worker that still owes a report, add `--no-expect-report`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"The message to submit.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report, and Pane keeps waiting for that report.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane agents send --pane <pane-id> --text \\\"$(cat review-notes.md)\\\" --as-file-pointer --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"notes\": [\n \"`delivered` is true when the agent took the message or queued it for after its current turn; `delivery` says which (`taken` or `queued`) and how Pane knows (`transcript` or `screen`). Never resend a delivered message.\",\n \"It types the text, then queues with Tab when Codex is working or submits with Enter when ready. It is for messages, not keys. To answer a menu, use `runpane panels input --panel <panel-id> --keys down,enter --yes`, then check the screen with `runpane agents status`.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\"\n ]\n },\n \"report\": {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"details\": \"Run this as a delegated worker when you finish, fail, or get blocked, as the last step of your task. The orchestrator receives it as an agent.report watch event and treats it as the completion signal. It prints whether anyone was waiting for it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"required\": true,\n \"description\": \"ready: work is ready for review; done: finished with nothing to review; blocked: needs an answer; failed: could not finish.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"required\": false,\n \"description\": \"Pull request number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"required\": false,\n \"description\": \"Head commit the report is about, 7-40 hex characters.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the summary from a file or stdin; the absolute path is kept as summaryPath.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What you need answered; required with --state blocked.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id when not running inside a Pane terminal (PANE_SESSION_ID).\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id when not running inside a Pane terminal (PANE_PANEL_ID).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane report --state blocked --question \\\"Should the migration drop the old column?\\\"\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"notes\": [\n \"Inside a Pane terminal the report is for the current panel (PANE_SESSION_ID and PANE_PANEL_ID). Explicit --panel (with an optional --pane) wins; with neither, the command fails with a clear error.\",\n \"Each report replaces the panel's previous one and survives a Pane restart.\",\n \"agent.report is in the default kinds of `runpane watch --follow` and `--until-event`, and arrives from any panel, a shell tab included. REPORT lines skip the --min-interval batch.\",\n \"The output says who received it: \\\"Delivered to N waiting watcher(s).\\\" counts watches waiting at that moment (liveWatchers in JSON). With no Session and nobody waiting it adds that the report is stored on the panel (runpane agents status --panel <id>) and that a named-cursor watch (runpane watch --as <name> --kinds agent.report) still receives it when it next waits. Zero is not a failure; the exit code is 0 either way.\",\n \"With --state blocked and --question, the person sees an answerable card at once; it rings once the orchestrator relays it or after 10 minutes. The answer reaches this tab as `answer: Q-n → <answer> (by you)`. `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too.\"\n ]\n },\n \"sessions list\": {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"details\": \"List durable named orchestration Sessions.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"notes\": []\n },\n \"sessions create\": {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"details\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Create-time isPinned requires an updated Pane app. Older apps may ignore the field; use sessions pin after creation and read back the result.\",\n \"runtime appears only on WSL Sessions (\\\"wsl\\\", with wslDistribution, Windows only). A Session on the native host has no runtime field on any OS; \\\"windows\\\" is accepted on input as a legacy name for the native host and is not stored.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"Example: {\\\"name\\\": \\\"Release QA\\\", \\\"goal\\\": \\\"Ship 2.5\\\", \\\"decisions\\\": [\\\"Cut from main\\\"], \\\"blockers\\\": [], \\\"outputs\\\": [{\\\"label\\\": \\\"Release PR\\\", \\\"url\\\": \\\"https://github.com/greenfield-inc/Pane/pull/1\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"pull-request\\\"}]}\"\n ]\n },\n \"sessions get\": {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"details\": \"Read one durable named orchestration Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions update\": {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"details\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"archived: true also archives every Pane associated with the Session, as `panes archive --pane` would. A Pane that check refuses stays open (agent-busy, unsent-input, uncommitted-changes, unpushed-commits, uncommitted-and-unpushed, status-unknown), and so does one whose archive fails. The result's `panes` lists `archivedPaneIds`, `keptPanes` (with `code`) and `failedPanes` (with `error`); check a kept Pane with `panes archive --pane <id> --dry-run --json`. Undo is the person's: `panes restore`.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"update only: report is {\\\"summary\\\": non-empty string, \\\"status\\\": reported|verified, \\\"evidence\\\": [at least one link], \\\"reportedAt\\\": ISO 8601 date, \\\"provenance\\\": non-empty string}; report null clears it.\",\n \"Example: {\\\"expectedRevision\\\": 4, \\\"nextAction\\\": \\\"Merge after CI\\\", \\\"decisions\\\": [\\\"Cut from main\\\", \\\"Skip beta\\\"], \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"evidence\\\", \\\"provenance\\\": \\\"qa agent\\\"}], \\\"report\\\": {\\\"summary\\\": \\\"QA passed\\\", \\\"status\\\": \\\"verified\\\", \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\"}], \\\"reportedAt\\\": \\\"2026-10-14T09:31:00Z\\\", \\\"provenance\\\": \\\"qa agent\\\"}}\"\n ]\n },\n \"sessions set-agent\": {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"details\": \"Switch the durable terminal agent for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions associate\": {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"details\": \"Associate a user-visible Pane with a named Session. `panes create` and `panes adopt` already do this automatically when run inside a Session orchestrator; use this for existing Panes or after `--no-associate`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions detach\": {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"details\": \"Detach a Pane from a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions overview\": {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"details\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"notes\": [\n \"The result includes `locks`: named locks scoped to the Session or held by its Panes (see `runpane lock`).\",\n \"Each `panes[].panels[]` entry has `type`, the tab's panel type (`terminal`, `browser`, `editor`, `explorer`, `diff`, `logs`, `notes`, `dashboard` or `setup-tasks`). A Pane linked whole lists every tab, with `explorer` and `diff` last; a link that names tabs lists exactly those. Only terminals carry an agent `state`; other tabs report `unknown` and do not change the Session's `status`. A missing panel has no `type`, and daemons that predate it omit it.\",\n \"Each `panes[].panels[]` entry has `report`, that panel's latest `runpane report` (state, `pr`, `head`, `question`, `summary`, `summaryTruncated`, `summaryPath`, `reportedAt`). When the summary is a complete JSON object, `report.data` holds it exactly as the worker gave it; Pane never reads or renames its fields. A plain-text, array or scalar summary, invalid JSON, JSON nested more than 64 levels deep, or a truncated summary (`summaryTruncated`) has no `data`. A panel that never reported has no `report` but keeps `state` and `lastActivityAt`. `panes[].report` is still the newest report among the Pane's panels, with its `panelId`; daemons that predate per-panel reports omit `panels[].report`.\"\n ]\n },\n \"lock acquire\": {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"details\": \"Run this before using a resource that only one agent may use at a time, such as a shared test account, and release it when done. The owner is the calling Pane and panel ($PANE_SESSION_ID and $PANE_PANEL_ID, or --pane/--panel). The lock is scoped to the owner's Session when its Pane belongs to one, so two Sessions can each hold `testing-account`; otherwise it is global. It succeeds when the lock is free, expired, or already yours (which renews the TTL). Otherwise it returns ok:false with heldBy and expiresAt at once, or with --wait blocks in the daemon until the lock is released, expires, or its owner's panel exits or Pane is archived.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"required\": true,\n \"description\": \"How long the lock is held before it expires, such as 90s, 30m, or 2h; at most 24h. Acquire again before it runs out to renew.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Wait this long for the lock to come free. The CLI waits in daemon calls of up to 120 seconds each.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What the lock is for, shown to other agents. Required outside a Pane terminal, where it also names the owner.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock acquire --name staging-db --ttl 10m --json\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"notes\": [\n \"Exit status is 0 only when the lock is yours; ok:false (exit 1) means another owner holds it, named by heldBy until expiresAt.\",\n \"Release the lock as soon as you are done with the resource: `runpane lock release --name <name>`. If you interrupt a waiting acquire, the lock can still be granted to you for up to two minutes; release it or let the TTL expire.\",\n \"Pane releases the lock when its TTL runs out, when the owner panel exits, or when the owner Pane is archived. Quitting Pane does not release it; locks survive a restart.\"\n ]\n },\n \"lock release\": {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"details\": \"Only the owner can release a lock. --force releases another owner's lock, for example a stuck worker's; use --session to name a Session-scoped lock from outside that Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Release the lock even though another owner holds it.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session whose lock to release; defaults to the caller's Session, or global.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Outside a Pane terminal, the note the lock was acquired with.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock release --name testing-account --json\",\n \"runpane lock release --name testing-account --session \\\"<session-id-or-name>\\\" --force --json\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"notes\": [\n \"released:false with ok:true means no such lock was held. ok:false with reason not-owner (exit 1) means another owner holds it.\"\n ]\n },\n \"lock list\": {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"details\": \"List every held lock, or with --session the locks scoped to that Session plus those its Panes hold. `runpane sessions overview` includes the same Session locks.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock list --json\",\n \"runpane lock list --session \\\"<session-id-or-name>\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"notes\": []\n },\n \"workspace list\": {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"details\": \"Shows this machine's workspace state, then each other Mac, Windows, and Linux machine on your tailnet with its OS, online state, and shell. Your own machines come first; other people's machines carry their owner's login and answer only when their owner lets you in.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace list --json\"\n ],\n \"notes\": [\n \"A machine has joined when Pane runs there with workspaces on; it answers on https://<machine>.<tailnet>.ts.net:8443.\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n \"workspace enable\": {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"details\": \"Serves this Pane's daemon with tailscale serve on port 8443 in HTTP proxy mode. Only your own Tailscale login is accepted unless you set Who can connect to Everyone on tailnet; nothing is opened to the internet.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace enable\"\n ],\n \"notes\": [\n \"Workspaces are on by default when Tailscale is installed and signed in; this undoes runpane workspace disable.\"\n ]\n },\n \"workspace disable\": {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"details\": \"Turns workspaces off for this machine until runpane workspace enable. The remote daemon on port 443 and pairing codes are unaffected.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace disable\"\n ],\n \"notes\": [\n \"Other machines can no longer reach this one; this machine can still reach theirs.\"\n ]\n },\n \"workspace read\": {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"details\": \"Reads the same way on every OS. On Windows, /mnt/c/... reaches C:\\\\... and WSL paths such as /home/<user>/... are read through \\\\\\\\wsl.localhost\\\\<distro>\\\\. Directories list their entries.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form; ~ is that machine's home.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox read 'C:\\\\Users\\\\me\\\\.pane\\\\plans\\\\topic\\\\index.html'\",\n \"runpane workspace read /mnt/c/Users/me/notes.md\"\n ],\n \"notes\": [\n \"--json returns content with encoding utf8 or base64.\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n \"workspace write\": {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"details\": \"Takes the same path forms as workspace read. The content replaces the file.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text that replaces the file; an empty string empties it. Without it, the CLI writes stdin. The MCP tool requires it.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"echo done | runpane workspace parsa-devbox write '~/notes/status.md'\"\n ],\n \"notes\": [\n \"Binary stdin is sent as base64 and written byte for byte.\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n \"workspace exec\": {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"details\": \"Runs in the shell Pane uses for terminals on that machine (PowerShell or Git Bash on Windows, the login shell elsewhere), with that Pane's runpane first on PATH. The exit code becomes runpane's exit code. Without --, the command starts at the first word that is not an exec option. Without <machine>, a --cwd that cannot exist here picks the machine.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to let a --cwd that cannot exist here pick the one joined machine it fits.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Working directory on that machine, in any path form; defaults to its home.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Stop the command after this long (default 120000, at most 540000).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command line, run by that machine's shell.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox exec --json -- git -C C:\\\\src\\\\app status --short\"\n ],\n \"notes\": [\n \"This is SSH-level power: a local agent's sandbox and permission prompts see only runpane workspace exec, not what runs on the other machine.\",\n \"Pass the command as one quoted string; several words after -- are joined with spaces, so their own quoting is lost.\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n \"handoff\": {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"details\": \"Two steps. The agent writes the note, because only it has the context: run `runpane handoff --template > ~/handoff.md` (outside the checkout) and fill in every section (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state), writing \\\"None\\\" where one does not apply. Then `runpane handoff \\\"<destination>\\\" --note-file ~/handoff.md` moves it: it rejects a note with a missing or empty section, refuses an unpushed branch unless --push, finds the destination's saved repository with the same remote, fetches the branch there, writes the note to `~/.pane/handoffs/` on that machine with its origin in the front matter and receiver instructions appended, and starts the agent in a new Pane branched from your branch. When you run in a Pane tab and the receiver is your own agent, it carries your session instead of starting fresh: another machine gets a copy of the session's files in its agent store and resumes the same id; this machine resumes a fork. The receiver starts with no prompt, and the CLI checks it resumed and shows the session's last message, then tells it where the session moved. The receiver verifies the head commit, continues from Next steps, pushes to your branch, and reports back to your panel with `runpane workspace <your machine> panels submit`. Run from a Session's terminal (its folder is outside git), handoff moves the Session instead: it skips git, writes the note to the other machine the same way, and creates a Session there with the same name, goal and agent (numbered \\\"<name> 2\\\" if the name is taken), whose context and next action point at the note; a same-agent Session also gets your conversation's files and resumes it when first opened. The sender Session stays as it is; the new one waits for your first message. A Session cannot be handed off to the machine it is on.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<destination>\",\n \"required\": false,\n \"description\": \"Freeform text naming the agent (claude, codex, cursor), and optionally the model, the effort, and the machine: a Tailscale machine name or unique prefix (list them with `runpane workspace list`), or \\\"this machine\\\". Required unless --agent is given. Examples: \\\"codex gpt-5 high on parsas-macbook-pro\\\", \\\"claude opus on parsa-devbox\\\", \\\"cursor on this machine\\\".\"\n },\n {\n \"name\": \"--template\",\n \"required\": false,\n \"description\": \"Print the note template, with this checkout's (or Session's) origin and git state in its front matter, and exit.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"The note to check and send; required unless --template or --dry-run. Without it, --dry-run checks only the destination and git state.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Destination machine; overrides the text.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Receiving agent; overrides the text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"required\": false,\n \"description\": \"Model for the receiving agent, such as opus, sonnet, gpt-5, or claude-opus-5-5; overrides the text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"required\": false,\n \"description\": \"minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Saved repository on the destination. Defaults to the one with a remote for this repository.\"\n },\n {\n \"name\": \"--push\",\n \"required\": false,\n \"description\": \"Commit uncommitted work (except the note) as WIP and push the branch before sending; never forces. Without it, an unpushed branch stops the handoff.\"\n },\n {\n \"name\": \"--note-only\",\n \"required\": false,\n \"description\": \"Start a fresh agent with the note instead of carrying your own session. Without it, a handoff to the same agent carries and resumes your session.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check destination daemon reachability and resolve its saved repository name, path, environment and OS; print the note check and git state without committing, pushing, fetching, writing notes or creating a Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane handoff --template > ~/handoff.md\",\n \"runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \"runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run --json\",\n \"runpane handoff --machine build-server --agent cursor --note-file ~/handoff.md\"\n ],\n \"notes\": [\n \"Another machine is reached through runpane workspace (Tailscale); it needs Pane running there with workspaces enabled and the repository saved. Repository listing and Pane creation call the reached daemon directly; no global destination CLI installation is required.\",\n \"Destination preflight runs before sender commit/push. Inside WSL, an absent or refused local Linux daemon can fall back to its Windows host through reachable Windows Tailscale and enabled workspaces; explicit Pane directory selection keeps its instance. Select a native Windows saved repository; WSL saved repositories are rejected before sending.\",\n \"A successful handoff requires initialInput.verifiedSubmitted and delivery taken or queued without blocked/error evidence. Unverified delivery exits nonzero with the created Pane, panel and a panels screen inspection command; inspect before retrying.\",\n \"From a Session's terminal (PANE_ORCHESTRATION_SESSION_ID set, folder outside git) the receiver is a Session, not a Pane: it needs only runpane:sessions:create and runpane:machine:write on the destination, pushes nothing, and refuses this machine before sending. JSON output then has receiver.route \\\"session\\\", session {id, name, machine} and no git field; a dry run reports the name the Session would get.\",\n \"Write the note for a reader with no context: quote errors and commands exactly, and record approaches that failed so they are not retried. Leave the front matter to the CLI.\",\n \"Model and effort become agent flags: claude --model/--effort, codex -m and -c model_reasoning_effort, cursor-agent --model. Cursor effort is not supported and is rejected before side effects.\",\n \"Run it from the checkout being handed off. On another machine the receiver works in a new Pane; on this machine, from the Pane that owns this checkout, it works in a new tab of that Pane (the result's receiver.route and receiver.reason say which). Either way it pushes back to your branch, so stop changing that branch yourself.\",\n \"JSON continuity: { carried, result: continued | inconclusive | not-continued | fresh-agent, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }; a dry run sets carried, sessionId, fork, files and bytes and no result. inconclusive exits 0 and next says to ask the receiver \\\"What were you last working on? Answer in one or two sentences.\\\"; not-continued exits 1, keeps the receiver and gives the --note-only command for the person to choose. After continued or inconclusive, the sender ends its turn and makes no more changes on the branch. From a Session the result is inconclusive until the new Session is opened. A Cursor session is carried only to another machine (best effort); on this machine the result is not-continued, since a copied Cursor chat still writes to the original. The destination's agent store is assumed at its default location (~/.claude, ~/.codex, ~/.cursor); a copy already there that holds history the sender's lacks, or that an agent there is running, is never overwritten (not-continued). checks.originalAppended is set when the running sender's own turn appended to its transcript during the fork; every byte it held before is unchanged. A destination Pane too old to resume a carried session is refused before anything is sent (ERR_HANDOFF_RESUME_UNSUPPORTED).\"\n ]\n },\n \"sessions unpin\": {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pin\": {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pause\": {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"details\": \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived; a chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`, until `sessions resume` resumes them. A Session reads as paused (`sessions get` `session.paused`) while its chat and all of its Panes are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pause --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"sessions resume\": {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"details\": \"Resumes the paused tabs of a Session's chat and Panes. An archived Session is refused. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions resume --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"pr watch\": {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"details\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<full-sha>\",\n \"required\": false,\n \"description\": \"Pin success to this 40-character SHA; otherwise pin the first observed head.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr unwatch\": {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"details\": \"Remove a Pane’s explicit PR registration.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr list\": {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"details\": \"List PR registrations and stopped watches.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"inbox\": {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"details\": \"An orchestrator runs it when it starts or resumes, to catch reports, missing reports, questions and answers that arrived while it was down. Pane never adds the inbox to the orchestrator's turns.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session; defaults to PANE_ORCHESTRATION_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox --session \\\"$PANE_ORCHESTRATION_SESSION_ID\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"notes\": [\n \"Items: report (agent.report), report.missing (agent.report.missing), and blocked (agent.blocked, with the prompt's screen lines). Reports coalesce per panel, blockers clear when the panel moves on, and each Session keeps at most 200 items until acknowledged or superseded. A journal replay watermark recovers committed outcomes after restart within journal retention.\",\n \"A newer report replaces the panel's older report or missing report.\",\n \"`answers` lists what the Session's workers were told, kept 7 days. One with no `decision` still needs recording: `runpane inbox ack <id> --decision <D-n>`. An older Pane leaves `answers` out.\"\n ]\n },\n \"inbox ack\": {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"details\": \"Acknowledge each item once you have acted on it, so the hook stops repeating it. With --decision, also record the D-n of an answer the worker already got.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<item-id>...\",\n \"required\": false,\n \"description\": \"Item ids from `runpane inbox`.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"required\": false,\n \"description\": \"Record this D-n on each named item, such as D-147. Not with --all.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Only clear items of this Session; required with --all.\"\n },\n {\n \"name\": \"--all\",\n \"required\": false,\n \"description\": \"Clear every item of the Session.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox ack in-1a2b3c4d --json\",\n \"runpane inbox ack in-1a2b3c4d --decision D-147 --json\"\n ],\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"notes\": [\n \"Unknown ids are reported under `unknown`; acknowledging twice is harmless.\",\n \"An id already cleared whose answer has no decision still records it.\"\n ]\n },\n \"loops list\": {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"details\": \"Lists every loop: its id, target (projectId for a repo, or orchestrationSessionId for a Session), cron and scheduleText, enabled, nextRunAtMs (null while paused), and its newest runs. A run's sessionId is its Pane (null for a Session loop's runs); read its progress with `runpane agents status --pane <id>`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops list --json\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"notes\": [\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops create\": {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"With --repo, each run is a new Pane in a fresh worktree branched from the repo's default base branch, started like `runpane panes create` with the agent or command and prompt given here. With --session, each run sends the prompt to that Session's agent instead of creating a Pane; a Session loop needs --agent and --prompt and takes no --tool-command. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": true,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--paused\",\n \"required\": false,\n \"description\": \"Create the loop paused.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops create --repo <repo> --name dependency-check --cron \\\"0 9 * * 1\\\" --tool-command \\\"pnpm test\\\" --yes --json\",\n \"runpane loops create --session new --name morning-triage --cron \\\"0 9 * * 1-5\\\" --agent claude --prompt \\\"Triage the new issues\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": [\n \"Saving rejects a cron whose runs fall less than 60 minutes apart. Check one first with --dry-run.\"\n ]\n },\n \"loops update\": {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"Reads the loop, replaces the values you pass, and saves it. Passing --agent clears --tool-command and the other way round; --repo and --session replace each other the same way. Pause and resume with `loops pause` and `loops resume`. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": false,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops update --loop <loop-id> --cron \\\"0 4 * * *\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": []\n },\n \"loops run\": {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"details\": \"Starts a run now, as a new Pane or a message to the loop's Session, even while the loop is paused or another run is still going. The schedule stays the same, and the run counts toward the 10 kept runs.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops run --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": [\n \"The result is the loop; `runs[0]` is the new run, with `status` started or failed and `sessionId` its Pane (null for a Session loop).\",\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops pause\": {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"details\": \"A paused loop starts no scheduled runs. `loops run` still starts one by hand.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops pause --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops resume\": {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"details\": \"Runs resume at the next scheduled time. Runs missed while paused are not caught up.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops resume --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops delete\": {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"details\": \"Deletes the loop and its schedule.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops delete --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"notes\": []\n },\n \"notify\": {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"details\": \"Prefer this to asking in the terminal whenever you need the person: to pick between options (ui options), to approve or deny an action (ui approve), to type a short answer (ui ask), or to tell them something that needs no answer (ui info). Pane shows it on screen even while the person works in another app, and the call waits until they answer, however long that takes. info returns at once.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"required\": false,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer. info: an update that returns at once. Required unless resume, took or update.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"info\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The question or update in one line, about 80 characters; long titles wrap to 3 lines. Required unless resume, took or update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Why it matters, shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there unless questions: 1 to 9 choices. The answer is the chosen label. Say what each does in detail, and mark the one you recommend. With update, a reworded choice adds was, its old label.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only: a preformatted block, such as the command or SQL to approve, behind a disclosure.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only, with detail: the disclosure's label, such as \\\"Show the SQL\\\"; up to 40 characters. Defaults to \\\"Show details\\\".\",\n \"schema\": {\n \"type\": \"string\",\n \"maxLength\": 40\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui info only: one button that opens a URL.\",\n \"schema\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files, pages or approvals, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https. Another approval is {label, approval}, its id or Q-n.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"required\": false,\n \"description\": \"A Session orchestrator relaying a worker's question: the waiting item's id from `runpane inbox`. The same card gains these choices, context and links instead of a second card showing. ui and title are still required.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Autopilot's own item: the Session it is about, shown as its connected Session. Without it, the item links to Autopilot's chat.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, instead of choices: 2 to 4 questions asked at once, each with 1 to 9 choices and one pick. The result's answers holds one label per question; answer joins them with \\\" · \\\".\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n }\n }\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After a reply result or a `reply:` line: the approval's id or Q-n, with tookAs answer (record the reply as the answer) or question (it asked something; find out, then call update). Pass no question fields.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"required\": false,\n \"description\": \"With took: answer or question. On the command line it can follow the id instead (`--took Q-20 answer`).\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n }\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With took question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After you took a reply as a question and found out: the approval's id or Q-n. Saves a new version with any of title, context, choices, questions and links you pass (the rest stay), shows it as updated, and waits on it like any notify call. Needs asked and whatChanged; takes no ui.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: what changed, one line per change.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"The id from a waiting result: keep waiting on that question. Pass no other fields.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane notify --ui options --title \\\"Which database for the cache?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]' --json\",\n \"runpane notify --ui approve --title \\\"Drop the legacy_users table?\\\" --detail \\\"DROP TABLE legacy_users;\\\" --detail-label \\\"Show the SQL\\\" --json\",\n \"runpane notify --ui ask --title \\\"Which branch should I base this on?\\\" --suggestions '[\\\"main\\\",\\\"release\\\"]' --json\",\n \"runpane notify --ui info --title \\\"PR #812 is ready for review\\\" --action '{\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}' --json\",\n \"runpane notify --ui approve --title \\\"Ship the release notes?\\\" --links '[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR #812\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}]' --json\",\n \"runpane notify --relays in-1a2b3c4d --ui options --title \\\"Q-39: Run the script?\\\" --choices '[{\\\"label\\\":\\\"Run it\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]' --json\",\n \"runpane notify --ui options --title \\\"Ship 2.5 today: which parts go out?\\\" --questions '[{\\\"title\\\":\\\"Release notes\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Ship as written\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]},{\\\"title\\\":\\\"Intel Mac build\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Include it\\\"},{\\\"label\\\":\\\"Skip it\\\"}]}]' --json\",\n \"runpane notify --took Q-20 answer --json\",\n \"runpane notify --took Q-20 question --status \\\"Checking whether it can be undone\\\" --json\",\n \"runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: Settings > Search turns it off again\\\" --context \\\"Search takes 4 s; the index brings it under 100 ms. It can be undone from Settings.\\\" --json\",\n \"runpane notify --resume n-4f2a --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"Write the card the way the pane MCP server's instructions say: in plain words, what the work is for, what just happened, and what you need.\",\n \"Results: options returns {answer: <chosen label>, note?}, with questions also answers (one label per question); approve returns {approved: true|false, note?}; ask returns {answer}; info returns {delivered: \\\"screen\\\"|\\\"inbox\\\"}.\",\n \"A result of {reply, picks?, id, replyId, next} means the person typed a reply instead of only picking: it may answer the question or ask one. Read it, then call `runpane notify --took <replyId> answer` to record it as the answer, or `runpane notify --took <replyId> question` and find out, then `runpane notify --update <replyId> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with whatever changed, instead of asking again. With no open call the reply reaches your tab as a `reply:` line; `correction:` lines fix how it was taken. --took returns {took, id, next?}. Use its replyId rather than the Q-n: only this call was given it, so it works from outside a Pane tab too.\",\n \"Only the reply's reader may run took and update: the asker, the orchestrator that relayed it, or Autopilot. Anyone else gets ERR_NOTIFY_NOT_READER.\",\n \"A result of {waiting: true, id} means the question is still on screen and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting. After 15 minutes on this loop Pane gives up and returns unavailable.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now (Pane is not running, Notify is off, or the wait ended): ask in the terminal instead. It is a result, not an error.\",\n \"Inside a Pane terminal the question names its Pane and panel (PANE_SESSION_ID and PANE_PANEL_ID); elsewhere it names the working directory.\",\n \"An older Pane ignores links and shows the card without them.\",\n \"With relays the call waits on the worker's card as on any notify, and the answer also goes straight to the worker's tab as `answer: Q-n → <answer> (by you)`. Then record it: `runpane inbox ack <item> --decision <D-n>`.\",\n \"Invalid input, such as choices with ui ask, 10 choices, or ui with took or update, fails with a message naming the field, and nothing is shown.\"\n ]\n },\n \"autopilot status\": {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"details\": \"Autopilot (experimental) is one always-running agent above every Session. It is not a Session: its run lives in <PANE_DIR>/autopilot/. Read this first to know whether it is on, entitled, and running.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot status --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"notes\": [\n \"Works while Autopilot is off (enabled: false), so a caller can tell off from broken.\"\n ]\n },\n \"autopilot waiting\": {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"details\": \"Autopilot's queue, read from each machine's table with no copy: every item waiting on the person on every machine it can reach, filed under its Session (or not in one), this machine's first, with who asked, how long it has waited, whether it is on the stack, and whether it is stale or quiet. `offline` names saved machines it can't reach, and `cantSee` names the person's Tailscale machines not saved here.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot waiting --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"Its ids go straight to `runpane autopilot answer`, `close`, `rewrite` and `stack push --item`, whichever machine holds the item.\",\n \"A `quiet` item is a worker's question its orchestrator still has first call on; a `stale` one has waited 3 days and no longer rings.\",\n \"An older Pane leaves `offline` and `cantSee` out.\"\n ]\n },\n \"autopilot stack\": {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"details\": \"The stack holds entries in the order Autopilot put them; one is shown at a time and the next comes up when the person resolves it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off. A resolved entry carries doneAt.\"\n ]\n },\n \"autopilot stack push\": {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"details\": \"Give exactly one of --item, --file, --url, or --session. A new entry never replaces the one on screen; --next puts it right after it. runpane resolves relative file paths (the entry's and its tabs') against the caller's directory before sending.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"A document to show: HTML loads as a page, anything else as text.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"An http(s) page or running app to show.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"A Session whose overview to show.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The entry's title; defaults to the item's, file's, or Session's own.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Autopilot's one line about why it is here.\"\n },\n {\n \"name\": \"--tab\",\n \"value\": \"<path-or-url>\",\n \"required\": false,\n \"description\": \"An extra tab: a file path, or an http(s) URL. Repeatable on the CLI.\"\n },\n {\n \"name\": \"--next\",\n \"required\": false,\n \"description\": \"Put it right after the entry on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack push --item n-1a2b3c --note \\\"The release orchestrator wants a go\\\" --json\",\n \"runpane autopilot stack push --file docs/plan.html --tab https://localhost:5173 --next --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_PAUSED (the person paused Autopilot; wait for Go on), AUTOPILOT_STACK_INVALID (no such item, file, URL, or Session).\",\n \"The result says where the entry went: position of total, and shown when it is on screen now.\"\n ]\n },\n \"autopilot answer\": {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"details\": \"Give exactly one of --choice, --approve, --deny or --text, and --because. The machine that holds the item delivers the answer at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the person can Override it. Skill behavior, not a setting: answer only when the way forward is clear and the item relates to your work with the person, or when the person told you to handle approvals.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"required\": false,\n \"description\": \"The chosen label of an options item.\"\n },\n {\n \"name\": \"--approve\",\n \"required\": false,\n \"description\": \"Approve an approve item.\"\n },\n {\n \"name\": \"--deny\",\n \"required\": false,\n \"description\": \"Deny an approve item.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A typed answer.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A note for the asker, with --choice, --approve or --deny.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why, recorded on the item: a D-n, a document or the person's words.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot answer n-1a2b3c --choice \\\"Run it\\\" --because \\\"D-22\\\" --json\",\n \"runpane autopilot answer n-1a2b3c --approve --because \\\"The person said to ship it\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot close\": {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"details\": \"The machine that holds the item closes it, tells its asker, and records \\\"Closed by Autopilot: <why>\\\" on every device. The person can reopen it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why it no longer needs the person, recorded on the item.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot close n-1a2b3c --reason \\\"The PR it asks about merged yesterday\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot rewrite\": {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"details\": \"The old item reads \\\"Closed by Autopilot: rewritten as …\\\" and the new one \\\"Rewritten by Autopilot from Q-n\\\". The rewrite stays on the old item's machine, and its answer goes to the original asker. runpane resolves relative --links paths against the caller's directory; a file must exist on the item's machine.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask>\",\n \"required\": true,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"The question in one line.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Background the person can open under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there: 1 to 9 choices. The answer is the chosen label. Mark at most the one you recommend.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files or pages, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot rewrite n-1a2b3c --ui options --title \\\"Q-39: Run the import on the copy first?\\\" --choices '[{\\\"label\\\":\\\"Copy first\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Skip\\\"}]' --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"The result's id is the new item's. Put that one on the stack.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"This repository is used with [Pane](https://runpane.com). Drive it with the CLI or the `pane` MCP server.\",\n \"\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.\",\n \"\",\n \"When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \\\"<question>\\\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.\",\n \"\",\n \"CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.\",\n \"\",\n \"MCP: packaged Pane registers a stdio server named `pane` with Claude Code, Codex, and Cursor. Check the connection with `claude mcp list`, `codex mcp list`, or `agent mcp list`. Cursor may ask you to enable `pane` with `agent mcp enable pane`. If tools are missing, add it in the agent's MCP settings: Claude Code `claude mcp add --scope user pane -- npx --yes runpane@latest mcp`; Codex (`~/.codex/config.toml`) table `[mcp_servers.pane]` with `command = \\\"npx\\\"` and `args = [\\\"--yes\\\", \\\"runpane@latest\\\", \\\"mcp\\\"]`; Cursor (`~/.cursor/mcp.json`) uses `mcpServers.pane` with the same `npx` command and args; any other stdio client uses them too.\"\n ],\n \"notifyGuidance\": {\n \"rules\": [\n \"Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.\",\n \"In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \\\"<question>\\\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.\",\n \"Ask only what you can't find out yourself.\",\n \"Put related questions in one card.\",\n \"Send `info` rarely, only for what the person must know now.\",\n \"Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.\",\n \"A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\\\"was\\\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.\",\n \"For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`.\"\n ],\n \"cardRule\": \"The person is juggling many tasks and has lost the thread. In plain words, say what the work is for (the outcome, not the file), what just happened, and what you need. For a decision, give the options and what each one means. Pane already shows which Pane and agent sent it.\",\n \"results\": \"`waiting` means call `notify` again with `resume`. `unavailable` means ask in the terminal; for a secret, ask the person to save it in Pane under Settings > Secrets.\",\n \"examples\": [\n {\n \"name\": \"Needs a decision\",\n \"ui\": \"approve\",\n \"bad\": [\n {\n \"title\": \"Approve the migration?\",\n \"context\": \"Ready to run 0042_add_index on prod. Proceed?\"\n }\n ],\n \"good\": {\n \"title\": \"Speed up slow search: run the database change?\",\n \"context\": \"Search in the app takes 4 s, and an index brings it under 100 ms. It changes the live database and can't easily be undone. Approve runs it now; Deny keeps it ready for later.\"\n }\n },\n {\n \"name\": \"Blocked\",\n \"ui\": \"options\",\n \"bad\": [\n {\n \"title\": \"Blocked\",\n \"context\": \"CI failing on macos-intel, hdiutil resource busy.\"\n }\n ],\n \"good\": {\n \"title\": \"The 2.5 release is stuck on a GitHub hiccup\",\n \"context\": \"The fixes you asked for pass everywhere except one Mac build machine, which errored. That's GitHub's machine, not our code.\",\n \"choices\": [\n \"Rerun it (about 15 min)\",\n \"Ship without the Intel Mac build\"\n ]\n }\n },\n {\n \"name\": \"Just so you know\",\n \"ui\": \"info\",\n \"bad\": [\n {\n \"title\": \"Done\",\n \"context\": \"Completed refactor of src/notify/queue.ts (head 06d310a).\"\n },\n {\n \"title\": \"PR #812 is ready\",\n \"context\": \"Tests pass.\"\n }\n ],\n \"good\": {\n \"title\": \"Questions from agents now wait until you answer\",\n \"context\": \"The fix you asked for this morning is done and tested. Next is your review: one click to merge.\"\n }\n },\n {\n \"name\": \"A question for ask\",\n \"ui\": \"ask\",\n \"bad\": [\n {\n \"title\": \"Which option?\",\n \"context\": \"1, 2 or 3?\"\n }\n ],\n \"good\": {\n \"title\": \"What should the release headline say?\",\n \"context\": \"You're writing the 2.5 release notes, and the headline is the first line people read. A few words is enough.\"\n }\n }\n ]\n }\n }\n}") diff --git a/packages/runpane/src/generated/contract.ts b/packages/runpane/src/generated/contract.ts index 966182aef..1e8ddcb0b 100644 --- a/packages/runpane/src/generated/contract.ts +++ b/packages/runpane/src/generated/contract.ts @@ -23623,7 +23623,7 @@ export const RUNPANE_CONTRACT = { "", "1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.", "", - "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"<question>\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", + "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"<question>\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", "", "CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.", "", @@ -23631,11 +23631,12 @@ export const RUNPANE_CONTRACT = { ], "notifyGuidance": { "rules": [ + "Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.", "In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \"<question>\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.", "Ask only what you can't find out yourself.", "Put related questions in one card.", "Send `info` rarely, only for what the person must know now.", - "Write the card as its Summary: a title of about 80 characters; context saying why it matters; each choice saying what it does, with one recommended; links to every file, page or approval it mentions.", + "Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.", "A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \"<their question>\" --what-changed \"<one line per change>\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\"was\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.", "For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`." ], diff --git a/shared/types/generatedRunpaneContract.ts b/shared/types/generatedRunpaneContract.ts index 966182aef..1e8ddcb0b 100644 --- a/shared/types/generatedRunpaneContract.ts +++ b/shared/types/generatedRunpaneContract.ts @@ -23623,7 +23623,7 @@ export const RUNPANE_CONTRACT = { "", "1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.", "", - "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"<question>\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", + "When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \"<question>\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.", "", "CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.", "", @@ -23631,11 +23631,12 @@ export const RUNPANE_CONTRACT = { ], "notifyGuidance": { "rules": [ + "Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.", "In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \"<question>\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.", "Ask only what you can't find out yourself.", "Put related questions in one card.", "Send `info` rarely, only for what the person must know now.", - "Write the card as its Summary: a title of about 80 characters; context saying why it matters; each choice saying what it does, with one recommended; links to every file, page or approval it mentions.", + "Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.", "A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \"<their question>\" --what-changed \"<one line per change>\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\"was\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.", "For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`." ], From 8f1e002db3eba666492443866e8470ddbfe45a95 Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 17:43:34 -0800 Subject: [PATCH 06/13] Chat shows one compact approval card with a pager; Send closes the column and a status line names the reader; Summary is a sandboxed page (P4-P7) --- .../approvals/ApprovalColumn.test.tsx | 10 +- .../components/approvals/ApprovalColumn.tsx | 208 ++++++++++-------- .../components/approvals/ApprovalsView.tsx | 12 +- .../approvals/SessionApprovalCards.tsx | 162 +++++++++----- .../components/approvals/SessionApprovals.tsx | 3 + .../components/approvals/approvalCard.test.ts | 49 +++-- .../src/components/approvals/approvalCard.ts | 113 +++++----- .../components/approvals/approvalGroups.ts | 21 +- .../src/components/approvals/summaryPage.ts | 52 +++++ .../src/components/chatTab/ChatTabRoute.tsx | 2 +- frontend/src/stores/approvalColumnStore.ts | 5 - main/src/services/notify/notifyQueue.ts | 2 + shared/approvalStatus.ts | 70 ++++++ 13 files changed, 469 insertions(+), 240 deletions(-) create mode 100644 frontend/src/components/approvals/summaryPage.ts create mode 100644 shared/approvalStatus.ts diff --git a/frontend/src/components/approvals/ApprovalColumn.test.tsx b/frontend/src/components/approvals/ApprovalColumn.test.tsx index 11090e004..9e3c9a004 100644 --- a/frontend/src/components/approvals/ApprovalColumn.test.tsx +++ b/frontend/src/components/approvals/ApprovalColumn.test.tsx @@ -38,26 +38,26 @@ function text(markup: string): string { } describe('ApprovalColumn', () => { - beforeEach(() => useApprovalColumnStore.setState({ open: {}, lastReplied: null })); + beforeEach(() => useApprovalColumnStore.setState({ open: {} })); it('waiting: the pager, tabs in order, both questions and one reply box', () => { const visible = text(show(q20, ['a', 'q20', 'b'], 'Back to doozy-overhaul')); expect(visible).toContain('2 of 3'); expect(visible).toContain('Back to doozy-overhaul'); - expect(visible).toMatch(/Questions & answers Summary PR #2480 doozy-overhaul's tab/); + expect(visible).toMatch(/Decide Summary PR #2480 doozy-overhaul's tab/); expect(visible).toContain('Question 1 of 2'); expect(visible).toContain('Question 2 of 2'); expect(visible).toContain('Recommended'); - expect(visible).toContain('Pick an option, or reply in your own words: an answer, or a question about it.'); + expect(visible).toContain('Recommended picks are pre-selected; change any before you send.'); }); it('Reading your reply…', () => { - expect(text(show({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'reading' } }))).toContain('Reading your reply…'); + expect(text(show({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'reading' } }))).toContain('doozy-overhaul is reading your reply…'); }); it('Taken as a question, with its fix, in amber', () => { const markup = show({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'question', reader: { kind: 'asker', name: 'doozy-overhaul' } } }); - expect(text(markup)).toContain('Taken as a question: asking doozy-overhaul. Getting more context…'); + expect(text(markup)).toContain('Taken as a question: doozy-overhaul is getting more context…'); expect(text(markup)).toContain('It was my answer'); expect(markup).toContain('data-tone="warning"'); }); diff --git a/frontend/src/components/approvals/ApprovalColumn.tsx b/frontend/src/components/approvals/ApprovalColumn.tsx index 68112ead4..296ce1c5f 100644 --- a/frontend/src/components/approvals/ApprovalColumn.tsx +++ b/frontend/src/components/approvals/ApprovalColumn.tsx @@ -8,12 +8,15 @@ import { leadingQn } from '../../../../shared/autopilotDecision'; import { useAllMachinesStore } from '../../stores/allMachinesStore'; import { useApprovalColumnStore, type ApprovalSurface } from '../../stores/approvalColumnStore'; import { AutopilotDocument, AutopilotWebPage } from '../autopilot/AutopilotDocument'; +import { isLightTheme, useTheme } from '../../contexts/ThemeContext'; +import { sentenceStart } from '../../../../shared/approvalStatus'; +import { readSummaryPage, type SummaryRead } from './summaryPage'; import { ItemCloseForm, NotifyCard } from '../notify/NotifyCard'; import { relayReadOnlyLine } from './approvalGroups'; import { relativeAge } from '../notify/notifyFormat'; import { useNow } from '../notify/useNow'; import { - askedBy, canSend, cardChip, replyBeingRead, cardSummary, cardTabs, dotTone, markedQuestions, nextAfterSend, previousVersion, questionsOf, replyLine, + askedBy, canSend, cardChip, replyBeingRead, cardTabs, dotTone, markedQuestions, previousVersion, questionsOf, recommendedPicks, replyLine, type CardChip, type CardQuestion, type CardTab, type ReplyLine, } from './approvalCard'; @@ -40,14 +43,12 @@ export interface ApprovalColumnProps { renderOrigin?: (item: NotifyItem) => ReactNode; /** The items to read instead of the Notify store's (tests). */ items?: readonly NotifyItem[]; + /** After an accepted Send (P7, D-344): a chat's column closes; the Approvals screen stays on the approval. */ + onSent?: () => void; } const NO_PICKS: string[] = []; -/** "the Infra orchestrator" opens a sentence as "The Infra orchestrator". */ -function capitalized(text: string): string { - return text.charAt(0).toUpperCase() + text.slice(1); -} /** Below this width the header keeps the pager and ✕, and drops the group's name, the dots and the chip. */ const NARROW_PX = 520; @@ -79,7 +80,7 @@ const TONE_BOX = { info: 'bg-status-info/10', } satisfies Record<ReplyLine['tone'], string>; -export function ApprovalColumn({ surface, itemId, order, onSelect, groupLabel, closeLabel, onClose, inChat, renderOrigin, items: given }: ApprovalColumnProps) { +export function ApprovalColumn({ surface, itemId, order, onSelect, groupLabel, closeLabel, onClose, inChat, renderOrigin, items: given, onSent }: ApprovalColumnProps) { const stored = useNotifyStore((state) => state.snapshot?.items); const items = given ?? stored; const byId = useMemo(() => new Map((items ?? []).map((item) => [item.id, item])), [items]); @@ -127,9 +128,9 @@ export function ApprovalColumn({ surface, itemId, order, onSelect, groupLabel, c nested ? <NestedApproval item={nested} onBack={() => openNested(surface, itemId, undefined)} /> // A new version starts afresh: its options may have changed under the picks. - : <QuestionsTab key={`${item.id}:${item.version ?? 1}`} item={item} order={pager} byId={byId} onSelect={onSelect} /> + : <QuestionsTab key={`${item.id}:${item.version ?? 1}`} item={item} onSent={onSent} tabs={tabs} onOpenTab={(key) => setTab(surface, itemId, key)} /> )} - {tab?.kind === 'summary' && <SummaryTab item={item} onOpenTab={(key) => setTab(surface, itemId, key)} tabs={tabs} />} + {tab?.kind === 'summary' && <SummaryTab key={item.id} item={item} />} {tab?.kind === 'link' && <LinkTab item={item} link={tab.link} onOpenApproval={(id) => openNested(surface, itemId, resolveApproval(items ?? [], id))} />} {tab?.kind === 'origin' && (renderOrigin?.(item) ?? <p className="p-6 text-[13px] text-text-tertiary">Asked in {item.source.name}.</p>)} </div> @@ -242,7 +243,7 @@ function TabBar({ tabs, active, onPick }: { tabs: CardTab[]; active: string; onP } /** Questions & answers: who asks, the question, how it changed, its options and the one reply box. */ -function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order: readonly string[]; byId: ReadonlyMap<string, NotifyItem>; onSelect: (id: string) => void }) { +function QuestionsTab({ item, onSent, tabs, onOpenTab }: { item: NotifyItem; onSent?: () => void; tabs: CardTab[]; onOpenTab: (key: string) => void }) { const now = useNow(); const questions = useMemo(() => markedQuestions(item), [item]); // The draft lives in the store, so it survives paging away and a refused Send (F-4). @@ -250,7 +251,9 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order const draft = useApprovalColumnStore((state) => state.drafts[draftKey]); const setDraft = useApprovalColumnStore((state) => state.setDraft); const clearSentDraft = useApprovalColumnStore((state) => state.clearSentDraft); - const picks = draft?.picks ?? NO_PICKS; + // Recommended picks start selected (D-345); the person changes any before sending. + const preselected = useMemo(() => recommendedPicks(item), [item]); + const picks = draft?.picks ?? (item.state === 'waiting' ? preselected : NO_PICKS); const text = draft?.text ?? ''; const setPicks = (update: (current: string[]) => string[]) => setDraft(draftKey, { picks: update(picks), text }); const setText = (value: string) => setDraft(draftKey, { picks, text: value }); @@ -261,8 +264,6 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order shown.current = true; return () => { shown.current = false; }; }, []); - const lastReplied = useApprovalColumnStore((state) => state.lastReplied); - const noteReplied = useApprovalColumnStore((state) => state.noteReplied); const waiting = item.state === 'waiting'; // What the column has no form of its own for: a secret, a prompt the terminal now holds, an update. const plain = item.ui === 'secret' || item.ui === 'info' || item.agentPrompt?.inTerminal || item.relay; @@ -284,22 +285,42 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order } // Refused (machine offline, already answered, still reading): the words and picks stay. if (!accepted) return; - if (typed && !item.agentPrompt) noteReplied(item.id); // Only what was sent is cleared: words or picks changed while Send was pending stay in the box (F-15). if (!clearSentDraft(draftKey, { picks, text })) return; - const next = shown.current ? nextAfterSend(order, item.id, byId) : undefined; - if (next) onSelect(next); + // Send closes a chat's column; the chat's status line says who reads it (P7, D-344). + if (shown.current) onSent?.(); }; const before = previousVersion(item); const earlier = [...item.versions ?? []].reverse(); - const replied = lastReplied && lastReplied !== item.id ? byId.get(lastReplied) : undefined; + const [showSent, setShowSent] = useState(false); + const replyBox = useRef<HTMLTextAreaElement>(null); + useGrowingBox(replyBox, text); + const references = tabs.filter((candidate): candidate is Extract<CardTab, { kind: 'link' }> => candidate.kind === 'link'); + const tidied = item.asSent; return ( <div className="flex h-full min-h-0 flex-col"> <div className="min-h-0 flex-1 overflow-y-auto px-6 py-5" data-testid="approval-qa"> <p className="text-[13px] text-text-tertiary">{[...askedBy(item), relativeAge(item.createdAt, now)].join(' · ')}</p> <h2 className="mt-1.5 text-[19px] font-semibold leading-snug text-text-primary" data-testid="approval-title">{item.title}</h2> - {item.context && <p className="mt-1.5 whitespace-pre-wrap text-[14px] leading-relaxed text-text-secondary">{item.context}</p>} + {tidied && ( + <div className="mt-2 rounded-lg bg-status-warning/10 px-3 py-2 text-[13px] text-status-warning-on-tint" data-testid="approval-tidied"> + <div className="flex items-start gap-2"> + <Sparkles className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" /> + <span className="min-w-0 flex-1">Tidied by Pane: sent as one {[...tidied.title].length}-character question. Split by rule; every word is the sender’s.</span> + <button type="button" className="shrink-0 font-medium hover:underline" aria-expanded={showSent} onClick={() => setShowSent((shown) => !shown)} data-testid="approval-show-as-sent"> + {showSent ? 'Hide' : 'Show as sent'} + </button> + </div> + {showSent && ( + <div className="mt-2 rounded-md bg-surface-primary px-3 py-2 text-text-primary" data-testid="approval-as-sent"> + <p className="whitespace-pre-wrap font-semibold">{tidied.title}</p> + {tidied.context && <p className="mt-1 whitespace-pre-wrap text-text-secondary">{tidied.context}</p>} + </div> + )} + </div> + )} + {item.context && <p className="mt-1.5 whitespace-pre-wrap text-[14px] leading-relaxed text-text-secondary" data-testid="approval-subtitle">{item.context}</p>} {item.revision && waiting && ( <section className="mt-4 rounded-xl border border-status-info/30 bg-status-info/10 p-4" data-testid="approval-revision"> <div className="flex items-center gap-2 text-[14px] font-semibold text-text-primary"> @@ -330,6 +351,7 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order key={`${at}:${question.title}`} question={question} label={questions.length > 1 ? `Question ${at + 1} of ${questions.length}` : 'Question'} + noDetail={tidied ? 'No description was sent.' : undefined} picked={picks[at]} disabled={!waiting} onPick={(label) => setPicks((current) => { const next = [...current]; next[at] = current[at] === label ? '' : label; return next; })} @@ -349,6 +371,24 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order ))} </div> )} + {references.length > 0 && ( + <div className="mt-4 flex flex-wrap items-center gap-1.5" data-testid="approval-references"> + <span className="mr-1 text-[13px] text-text-tertiary">References</span> + {references.map((reference) => { + const Icon = tabIcon(reference) ?? FileText; + return ( + <button key={reference.key} type="button" onClick={() => onOpenTab(reference.key)} + className="inline-flex max-w-[260px] items-center gap-1.5 rounded-md border border-border-secondary px-2 py-1 text-[13px] text-interactive hover:bg-surface-hover"> + <Icon className="h-3.5 w-3.5 shrink-0 text-text-tertiary" aria-hidden="true" /> + <span className="truncate">{reference.label}</span> + </button> + ); + })} + </div> + )} + <button type="button" onClick={() => onOpenTab('summary')} className="mt-3 inline-flex items-center gap-0.5 text-left text-[13.5px] text-interactive hover:underline" data-testid="approval-read-summary"> + Read the Summary: what’s being decided, the trade-offs and where things stand <ChevronRight className="h-3.5 w-3.5" aria-hidden="true" /> + </button> {before && earlier.length > 0 && ( <section className="mt-4 rounded-xl border border-border-secondary" data-testid="approval-earlier"> {earlier.map((version) => ( @@ -380,7 +420,6 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order {ended && line && <div className="shrink-0 border-t border-border-primary px-5 pb-1 pt-3"><ReplyNotice item={item} /></div>} {!plain && !ended && ( <div className="shrink-0 border-t border-border-primary px-5 pb-4 pt-3" data-testid="approval-reply"> - {replied && <ReplyNotice item={replied} onDismiss={() => noteReplied(null)} named />} {line && <ReplyNotice item={item} />} {item.state === 'sent' && ( <div className="mb-2 flex items-center gap-2 rounded-lg bg-surface-tertiary px-3 py-2 text-[13px] text-text-secondary"> @@ -390,13 +429,14 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order )} {waiting && !item.agentPrompt && !item.optionsOnly && ( <textarea + ref={replyBox} value={text} onChange={(event) => setText(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); void send(); } }} rows={2} aria-label="Reply" - placeholder="Reply: add a note, answer in your own words, or ask about it…" - className="w-full resize-none rounded-lg border border-border-primary bg-surface-primary px-3 py-2 text-[14px] text-text-primary placeholder:text-text-tertiary focus:border-border-focus focus:outline-none" + placeholder="Reply: pick above, add a note, answer in your own words, or ask about it…" + className="w-full resize-none overflow-y-auto rounded-lg border border-border-primary bg-surface-primary px-3 py-2 text-[14px] text-text-primary placeholder:text-text-tertiary focus:border-border-focus focus:outline-none" data-testid="approval-reply-box" /> )} @@ -405,7 +445,8 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order <div className="mt-2 flex items-center gap-3"> <p className="min-w-0 flex-1 text-[12.5px] text-text-tertiary"> {item.agentPrompt ? 'A permission prompt takes one of its options.' : item.optionsOnly ? 'This question takes one of its options.' - : replyBeingRead(item) ? `${capitalized(item.reply?.reader?.name ?? 'its reader')} is reading your last reply. You can send another once it says how it took it, or pick an option now.` : 'Pick an option, or reply in your own words: an answer, or a question about it.'} + : replyBeingRead(item) ? `${sentenceStart(item.reply?.reader?.name ?? item.source.name)} is reading your last reply. You can send another once it says how it took it, or pick an option now.` + : preselected.some(Boolean) ? 'Recommended picks are pre-selected; change any before you send.' : 'Pick an option, or reply in your own words: an answer, or a question about it.'} </p> {!item.agentPrompt && ( <button type="button" onClick={() => setClosing(true)} className="shrink-0 text-[12.5px] text-text-tertiary hover:text-text-primary" data-testid="approval-close-item">Close…</button> @@ -422,36 +463,31 @@ function QuestionsTab({ item, order, byId, onSelect }: { item: NotifyItem; order ); } -/** How a reply was taken, with its one-click fix; `named` when it's about another approval than the one open. */ -function ReplyNotice({ item, onDismiss, named = false }: { item: NotifyItem; onDismiss?: () => void; named?: boolean }) { +/** How the reply on this approval was taken, with its one-click fix. Shown only on its own approval (Q-343.3). */ +function ReplyNotice({ item }: { item: NotifyItem }) { const line = replyLine(item); if (!line) return null; const Icon = line.tone === 'success' ? Check : line.tone === 'warning' ? CircleAlert : Loader2; - const ref = /^\s*(Q-\d+)/.exec(item.title)?.[1] ?? item.title; return ( - <div className={cn('mb-2 flex items-start gap-2 rounded-lg px-3 py-2 text-[13px]', TONE_BOX[line.tone])} role="status" data-testid={named ? 'approval-last-reply' : 'approval-reply-line'} data-tone={line.tone}> + <div className={cn('mb-2 flex items-start gap-2 rounded-lg px-3 py-2 text-[13px]', TONE_BOX[line.tone])} role="status" data-testid="approval-reply-line" data-tone={line.tone}> <span className="mt-0.5 flex h-3.5 w-3.5 shrink-0 items-center justify-center" aria-hidden="true"><Icon className={cn('h-3.5 w-3.5', TONE_TEXT[line.tone])} /></span> - <span className={cn('min-w-0 flex-1 leading-snug', TONE_TEXT[line.tone])}> - {named && <span className="font-semibold">{ref} · </span>}{line.text} - </span> + <span className={cn('min-w-0 flex-1 leading-snug', TONE_TEXT[line.tone])}>{line.text}</span> {line.fix && ( <button type="button" className="shrink-0 font-medium text-interactive hover:underline" data-testid="approval-fix" onClick={() => runNotify({ type: 'fix-reply', id: item.id, as: line.fix?.as ?? 'answer' })}> {line.fix.label} </button> )} - {onDismiss && ( - <button type="button" aria-label="Dismiss" onClick={onDismiss} className="shrink-0 text-text-tertiary hover:text-text-primary"><X className="h-3.5 w-3.5" aria-hidden="true" /></button> - )} </div> ); } -function QuestionBlock({ question, label, picked, disabled, onPick, showTitle }: { question: CardQuestion; label: string; picked?: string; disabled: boolean; onPick: (label: string) => void; showTitle: boolean }) { +function QuestionBlock({ question, label, picked, disabled, onPick, showTitle, noDetail }: { question: CardQuestion; label: string; picked?: string; disabled: boolean; onPick: (label: string) => void; showTitle: boolean; noDetail?: string }) { return ( <section className="mt-4 rounded-xl border border-border-secondary p-4" data-testid="approval-question"> <div className="text-[12px] font-medium uppercase tracking-wide text-text-tertiary">{label}</div> {showTitle && <p className="mt-1 text-[15px] font-semibold text-text-primary">{question.title}</p>} + {question.why && <p className="mt-0.5 text-[13px] leading-relaxed text-text-secondary" data-testid="approval-question-why">{question.why}</p>} <div className="mt-2 flex flex-col gap-2" role="radiogroup" aria-label={question.title}> {question.choices.map((choice) => { const selected = picked === choice.label; @@ -483,7 +519,8 @@ function QuestionBlock({ question, label, picked, disabled, onPick, showTitle }: {choice.mark === 'new' && <span className="rounded bg-status-success/10 px-1.5 py-px text-[11px] font-semibold uppercase tracking-wide text-status-success-on-tint" data-testid="approval-mark">New</span>} {choice.mark === 'reworded' && <span className="rounded bg-status-info/10 px-1.5 py-px text-[11px] font-semibold uppercase tracking-wide text-status-info-on-tint" data-testid="approval-mark">Reworded</span>} </span> - {choice.detail && <span className="mt-0.5 block text-[13px] leading-relaxed text-text-secondary">{choice.detail}</span>} + {choice.detail ? <span className="mt-0.5 block text-[13px] leading-relaxed text-text-secondary">{choice.detail}</span> + : noDetail && <span className="mt-0.5 block text-[13px] leading-relaxed text-text-tertiary">{noDetail}</span>} {choice.before && ( <span className="mt-1 block text-[12.5px] leading-relaxed text-text-tertiary line-through" data-testid="approval-was"> was: {choice.before.label !== choice.label ? `${choice.before.label}${choice.before.detail ? `: ${choice.before.detail}` : ''}` : choice.before.detail ?? ''} @@ -504,70 +541,45 @@ function QuestionBlock({ question, label, picked, disabled, onPick, showTitle }: ); } -/** Summary (D-225): written by Pane from the approval itself, the same way for every approval. */ -function SummaryTab({ item, tabs, onOpenTab }: { item: NotifyItem; tabs: CardTab[]; onOpenTab: (key: string) => void }) { - const summary = useMemo(() => cardSummary(item), [item]); - const heading = 'mt-5 text-[12px] font-semibold uppercase tracking-wide text-text-tertiary'; +/** + * Summary (P4): one HTML page, the asker's own or the one Pane writes from the card, read from the + * machine that holds the approval and shown with scripts and network off, as on the phone. + */ +function SummaryTab({ item }: { item: NotifyItem }) { + const { theme } = useTheme(); + const dark = !isLightTheme(theme); + const [page, setPage] = useState<SummaryRead | null>(null); + useEffect(() => { + let current = true; + setPage(null); + void readSummaryPage(item, dark ? 'dark' : 'light').then((read) => { if (current) setPage(read); }); + return () => { current = false; }; + // A new version rewrites the template; the item's other changes don't. + }, [item.id, item.version, item.state, item.reply?.state, dark]); // eslint-disable-line react-hooks/exhaustive-deps + const label = page?.kind === 'page' ? baseName(page.path) : 'Written by Pane'; return ( - <div className="h-full overflow-y-auto px-6 py-5 text-[14px] text-text-primary" data-testid="approval-summary"> - <p className="flex items-center gap-1.5 text-[12.5px] text-text-tertiary"><BookOpen className="h-3.5 w-3.5" aria-hidden="true" /> Summary · Pane writes this from the approval itself, in the same format for every approval</p> - <h3 className={heading}>Why it matters</h3> - <p className="mt-1.5 whitespace-pre-wrap leading-relaxed">{summary.why ?? 'The asker gave no reason.'}</p> - <h3 className={heading}>Recommendation</h3> - {summary.recommendation.length > 0 ? ( - <div className="mt-1.5 rounded-lg bg-status-success/10 px-3 py-2.5 leading-relaxed"> - {summary.recommendation.map((choice, at) => ( - <p key={at}><span className="font-semibold text-status-success-on-tint">{choice.label}</span>{choice.question ? ` for “${choice.question}”` : ''}, from {item.source.name}.</p> - ))} + <div className="absolute inset-0 flex flex-col p-3" data-testid="approval-summary"> + <div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border border-border-secondary"> + <div className="flex h-9 shrink-0 items-center gap-2 border-b border-border-secondary bg-surface-tertiary px-3 text-[12.5px] text-text-tertiary"> + <BookOpen className="h-3.5 w-3.5 shrink-0" aria-hidden="true" /> + <span className="min-w-0 truncate font-medium text-text-secondary" data-testid="approval-summary-source">{label}</span> + <span className="shrink-0">· sandboxed, no scripts</span> </div> - ) : <p className="mt-1.5 text-text-secondary">{item.source.name} makes no recommendation.</p>} - {summary.questions.length > 0 && ( - <> - <h3 className={heading}>{summary.questions.length > 1 ? 'The questions' : 'The choices'}</h3> - {summary.questions.map((question, at) => ( - <div key={at} className="mt-1.5"> - {summary.questions.length > 1 && <p className="font-semibold">{question.title}</p>} - <ul className="list-disc pl-5 leading-relaxed"> - {question.choices.map((choice) => <li key={choice.label}><span className="font-semibold">{choice.label}</span>{choice.detail ? `: ${choice.detail}` : ''}</li>)} - </ul> - </div> - ))} - </> - )} - {summary.references.length > 0 && ( - <> - <h3 className={heading}>References</h3> - <div className="mt-1.5 overflow-hidden rounded-lg border border-border-secondary"> - {summary.references.map((reference, at) => { - const tab = reference.kind === 'origin' ? tabs.find((candidate) => candidate.kind === 'origin') : tabs.find((candidate) => candidate.kind === 'link' && candidate.index === at); - return ( - <button key={at} type="button" disabled={!tab} onClick={() => tab && onOpenTab(tab.key)} - className="flex w-full items-center gap-2 border-b border-border-secondary px-3 py-2 text-left last:border-b-0 hover:bg-surface-hover disabled:hover:bg-transparent"> - <span className="min-w-0 flex-1 truncate font-semibold">{reference.label}</span> - <span className="min-w-0 max-w-[50%] truncate text-[12.5px] text-text-tertiary">{reference.kind === 'file' ? 'a file' : reference.kind === 'url' ? reference.detail : reference.detail}</span> - </button> - ); - })} - </div> - </> - )} - {summary.history.length > 1 && ( - <> - <h3 className={heading}>Rounds</h3> - <ol className="mt-1.5 flex flex-col gap-2" data-testid="approval-summary-history"> - {summary.history.map((round) => ( - <li key={round.version} className="leading-relaxed"> - <span className="font-semibold">v{round.version}</span> - {round.asked ? <> · you asked “{round.asked}”; {round.by} changed: {round.whatChanged.join('; ')}</> : ' · as first asked'} - </li> - ))} - </ol> - </> - )} + {page === null && <p className="m-auto text-[13px] text-text-tertiary">Loading the Summary…</p>} + {page?.kind === 'failed' && <p className="m-auto px-6 text-center text-[13px] text-text-secondary">{page.message}</p>} + {(page?.kind === 'page' || page?.kind === 'template') && ( + // sandbox with no allowances: no scripts, no forms, no navigation; the page's own policy blocks the network. + <iframe title={`Summary: ${item.title}`} sandbox="" srcDoc={page.html} className="min-h-0 w-full flex-1 border-0 bg-surface-primary" data-testid="approval-summary-frame" /> + )} + </div> </div> ); } +function baseName(file: string): string { + return file.replace(/#.*$/u, '').split(/[\\/]/u).pop() ?? file; +} + /** A reference: a file in the document viewer, a URL in the in-app browser, another approval in place. */ function LinkTab({ item, link, onOpenApproval }: { item: NotifyItem; link: NotifyLink; onOpenApproval: (id: string) => void }) { const activeMachineId = useAllMachinesStore((state) => state.entries.find((entry) => entry.active)?.id ?? LOCAL_MACHINE_ID); @@ -593,8 +605,20 @@ function NestedApproval({ item, onBack }: { item: NotifyItem; onBack: () => void <ArrowLeft className="h-3.5 w-3.5" aria-hidden="true" /> Back </button> <div className="min-h-0 flex-1"> - <QuestionsTab item={item} order={[item.id]} byId={new Map([[item.id, item]])} onSelect={() => undefined} /> + <QuestionsTab item={item} tabs={[]} onOpenTab={() => undefined} /> </div> </div> ); } + +/** The reply box grows with what's typed, up to half the column, then scrolls (P6). */ +function useGrowingBox(ref: RefObject<HTMLTextAreaElement | null>, value: string): void { + useEffect(() => { + const box = ref.current; + if (!box) return; + const column = box.closest('[data-testid="approval-column"]'); + const limit = Math.max(64, (column?.clientHeight ?? 600) / 2); + box.style.height = 'auto'; + box.style.height = `${Math.min(Math.max(box.scrollHeight, 64), limit)}px`; + }, [ref, value]); +} diff --git a/frontend/src/components/approvals/ApprovalsView.tsx b/frontend/src/components/approvals/ApprovalsView.tsx index 19e82d50a..973aa6107 100644 --- a/frontend/src/components/approvals/ApprovalsView.tsx +++ b/frontend/src/components/approvals/ApprovalsView.tsx @@ -1,5 +1,5 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from 'react'; -import { ArrowUp, ChevronDown, ChevronRight, CircleCheck, CircleHelp, ExternalLink, Folder, GitBranch, Inbox, MessageSquare, MessagesSquare, Navigation, ShieldAlert, WifiOff } from 'lucide-react'; +import { ArrowUp, ChevronDown, ChevronRight, CircleCheck, CircleHelp, ExternalLink, Folder, GitBranch, Inbox, MessageSquare, MessagesSquare, Navigation, Send, ShieldAlert, WifiOff } from 'lucide-react'; import type { NotifyItem } from '../../../../shared/types/notify'; import { LOCAL_MACHINE_ID } from '../../../../shared/types/machineConnections'; import { cn } from '../../utils/cn'; @@ -18,6 +18,7 @@ import { useNow } from '../notify/useNow'; import { SessionBeside } from '../PaneChatView'; import { ApprovalColumn, ApprovalChip } from './ApprovalColumn'; import { cardChip } from './approvalCard'; +import { approvalStatus } from '../../../../shared/approvalStatus'; import { APPROVALS_SORTS, approvalIdsInListOrder, approvalRowLines, besideOf, connectedOf, groupApprovals, groupCountText, offlineRowText, readApprovalsSort, saveApprovalsSort, type ApprovalGroup, type ApprovalsSort, type ApprovalsTab, type Beside, type OfflineRow } from './approvalGroups'; import { MachineContext } from './MachineContext'; import { handleApprovalsEscape } from './approvalsNavigation'; @@ -62,7 +63,7 @@ function useMachineMark() { } function GroupHeader({ group, mark, count }: { group: ApprovalGroup; mark: ReactNode; count: string }) { - const Icon = group.autopilot ? Navigation : group.project ? Folder : group.session ? MessageSquare : GitBranch; + const Icon = group.holder === 'you' ? Inbox : group.holder === 'agent' ? Send : group.autopilot ? Navigation : group.project ? Folder : group.session ? MessageSquare : GitBranch; return ( <div className={cn('flex items-center gap-2 border-b border-border-secondary bg-surface-secondary px-3 py-2 text-[12.5px] font-semibold', group.autopilot ? 'text-autopilot-on-tint' : 'text-text-primary')} data-testid="approvals-group"> <Icon className={cn('h-3.5 w-3.5 shrink-0', group.autopilot ? 'text-autopilot' : 'text-text-tertiary')} aria-hidden="true" /> @@ -81,6 +82,8 @@ function ItemRow({ item, now, selected = false, onSelect }: { item: NotifyItem; const done = item.state === 'answered' || item.state === 'expired' || item.state === 'info'; const { acted, origin } = approvalRowLines(item); const chip = cardChip(item); + // Done today: how a typed reply was taken, on its own row (Q-343.3). + const taken = item.state === 'answered' ? approvalStatus(item) : null; return ( <button type="button" @@ -107,6 +110,7 @@ function ItemRow({ item, now, selected = false, onSelect }: { item: NotifyItem; <span className="flex shrink-0 flex-col items-end gap-1"> <span className="text-[12px] tabular-nums text-text-tertiary">{relativeAge(item.resolvedAt ?? item.createdAt, now)}</span> {chip && chip.tone !== 'success' && <ApprovalChip chip={chip} testId="approvals-row-chip" />} + {taken?.kind === 'taken' && <ApprovalChip chip={{ tone: 'success', text: 'Taken as your answer' }} testId="approvals-row-chip" />} </span> </button> ); @@ -399,7 +403,7 @@ export function ApprovalsView({ paneView }: { paneView: ReactNode }) { <h1 className="text-[15px] font-semibold text-text-primary">Approvals</h1> <div role="tablist" aria-label="Approvals" className="inline-flex gap-0.5 rounded-[9px] bg-surface-tertiary p-[3px] text-[12.5px]"> {tabButton('waiting', 'Waiting', waiting)} - {tabButton('answered', 'Answered', answered)} + {tabButton('answered', 'Done today', answered)} </div> </header> {importError && ( @@ -486,7 +490,7 @@ export function ApprovalsView({ paneView }: { paneView: ReactNode }) { itemId={selected.id} order={order} onSelect={selectApproval} - groupLabel={tab === 'waiting' ? 'Approvals' : 'Answered'} + groupLabel={tab === 'waiting' ? 'Approvals' : 'Done today'} inChat={undefined} renderOrigin={() => origin} /> diff --git a/frontend/src/components/approvals/SessionApprovalCards.tsx b/frontend/src/components/approvals/SessionApprovalCards.tsx index ba0e1be2b..dd2feefc9 100644 --- a/frontend/src/components/approvals/SessionApprovalCards.tsx +++ b/frontend/src/components/approvals/SessionApprovalCards.tsx @@ -1,74 +1,138 @@ -import { ChevronRight, CircleAlert, Navigation } from 'lucide-react'; +import { useMemo, useState } from 'react'; +import { Check, ChevronLeft, ChevronRight, CircleAlert, Loader2, Navigation, Sparkles, X } from 'lucide-react'; import type { NotifyItem } from '../../../../shared/types/notify'; +import { approvalStatus, approvalQn, type ApprovalStatus } from '../../../../shared/approvalStatus'; import { cn } from '../../utils/cn'; -import { runNotify } from '../../stores/notifyStore'; +import { runNotify, selectNotifyItems, useNotifyStore } from '../../stores/notifyStore'; import { openSessionApproval, useApprovalColumnStore } from '../../stores/approvalColumnStore'; import { relativeAge } from '../notify/notifyFormat'; import { useNow } from '../notify/useNow'; -import { ApprovalChip } from './ApprovalColumn'; -import { cardChip, isBeingRead, questionsOf } from './approvalCard'; +import { dockApprovals, latestReplied, questionsOf, referenceCount } from './approvalCard'; import { useSessionApprovals } from './useSessionApprovals'; -/** The dock holds this many cards; the rest are one click away in the column. */ -const DOCKED_MAX = 2; - /** - * Light approval cards in a Session's orchestrator chat (P5, D-219): docked above the composer, - * each with its title, one line and, for a one-question approval, its options as buttons. A - * button answers it; Open shows it in the column on the right. + * A Session chat's approvals (P6, D-347): one compact card at a time above the composer, its + * title and subtitle across the card, and ‹ n of m ›, the reference count and Open on a bottom + * row. The options live in the column on the right, which Open shows; the two pagers stay in + * step. Above it, after a Send, one status line says who is reading the reply and how it was + * taken (P7). The chat's own AskUserQuestion is left out: the chat already shows Claude's card (P5). */ -export function SessionApprovalCards({ sessionId }: { sessionId: string }) { +export function SessionApprovalCards({ sessionId, chatPanelId }: { sessionId: string; chatPanelId?: string }) { const waiting = useSessionApprovals(sessionId); + const pager = useMemo(() => dockApprovals(waiting, chatPanelId), [waiting, chatPanelId]); const openId = useApprovalColumnStore((state) => state.open[`session:${sessionId}`]?.itemId); - if (waiting.length === 0) return null; - const shown = waiting.slice(-DOCKED_MAX); - const more = waiting.length - shown.length; + const [shownId, setShownId] = useState<string | undefined>(); + // The open card leads; else the one last paged to, while it still waits; else the first. + const currentId = [openId, shownId].find((id) => id !== undefined && pager.some((item) => item.id === id)) ?? pager[0]?.id; + const index = Math.max(0, pager.findIndex((item) => item.id === currentId)); + const item = pager[index]; + const step = (direction: 1 | -1) => { + if (pager.length < 2) return; + const next = pager[(index + direction + pager.length) % pager.length]; + setShownId(next.id); + // The column's pager follows when it's open. + if (openId) openSessionApproval(sessionId, next.id); + }; + const status = <ReplyStatusLine sessionId={sessionId} />; + if (!item) return <div className="mx-auto w-full max-w-[var(--thread-max-width)] px-6 empty:hidden">{status}</div>; return ( <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 px-6 pb-2" data-testid="session-approval-cards"> - {more > 0 && ( - <button type="button" onClick={() => openSessionApproval(sessionId, waiting[0].id)} className="self-start text-[12.5px] font-medium text-interactive hover:underline"> - {more} more {more === 1 ? 'approval' : 'approvals'} waiting · Open - </button> - )} - {shown.map((item) => <LightCard key={item.id} item={item} sessionId={sessionId} open={item.id === openId} />)} + {status} + <CompactCard item={item} index={index} total={pager.length} open={item.id === openId} onStep={step} + onOpen={(tab) => useApprovalColumnStore.getState().openApproval(`session:${sessionId}`, item.id, tab)} /> </div> ); } -function LightCard({ item, sessionId, open }: { item: NotifyItem; sessionId: string; open: boolean }) { +function CompactCard({ item, index, total, open, onStep, onOpen }: { + item: NotifyItem; index: number; total: number; open: boolean; onStep: (direction: 1 | -1) => void; onOpen: (tab?: string) => void; +}) { const now = useNow(); - const questions = questionsOf(item); - const chip = cardChip(item); - // One question with options answers from here; anything more opens the column. - const buttons = questions.length === 1 && item.ui === 'options' && !item.agentPrompt?.inTerminal && !isBeingRead(item) && !item.unreachable ? questions[0].choices : []; - const qn = /^\s*(Q-\d+)/.exec(item.title)?.[1]; - const who = item.source.autopilot ? 'Autopilot' : item.source.session?.orchestrator ? `${item.source.session.name} orchestrator` : `${item.source.name}${item.originKind === 'worker' ? ', relayed' : ''}`; + const count = questionsOf(item).length; + const qn = approvalQn(item); + const who = item.source.autopilot ? 'Autopilot' : item.source.session?.orchestrator ? `${item.source.session.name} orchestrator` : item.source.name; + const references = referenceCount(item); + const stepButton = 'inline-flex h-6 w-6 items-center justify-center rounded-md border border-border-secondary text-text-secondary hover:bg-surface-hover disabled:opacity-40'; return ( - <div className={cn('rounded-2xl bg-surface-primary px-3.5 pb-2.5 pt-3 shadow-card ring-1', open ? 'ring-2 ring-interactive' : item.source.autopilot ? 'ring-autopilot-border' : 'ring-border-secondary')} data-testid="session-approval-card" data-item-id={item.id}> - <div className="flex items-center gap-1.5 text-[12px] font-medium uppercase tracking-wide text-text-tertiary"> - {item.source.autopilot ? <Navigation className="h-3.5 w-3.5 text-autopilot" aria-hidden="true" /> : <CircleAlert className="h-3.5 w-3.5 text-status-warning" aria-hidden="true" />} - <span className="min-w-0 truncate">{item.source.autopilot ? 'Autopilot asks' : 'Approval'}{qn ? ` · ${qn}` : ''} · <span className="normal-case">{who}</span></span> - <span className="ml-auto shrink-0 normal-case tabular-nums">{relativeAge(item.createdAt, now)}</span> + // D-343: the open card is outlined in grey with an "open" label; blue is only for focus. + <div className={cn('rounded-2xl bg-surface-primary px-4 pb-2.5 pt-3 shadow-card ring-1', open ? 'ring-2 ring-border-hover' : item.source.autopilot ? 'ring-autopilot-border' : 'ring-border-secondary')} + data-testid="session-approval-card" data-item-id={item.id} data-open={open || undefined}> + <div className="flex items-center gap-1.5 text-[12.5px] text-text-tertiary"> + {/* Fixed box: the icon swaps, never its size. */} + <span className="flex h-3.5 w-3.5 shrink-0 items-center justify-center" aria-hidden="true"> + {item.source.autopilot ? <Navigation className="h-3.5 w-3.5 text-autopilot" /> : <CircleAlert className="h-3.5 w-3.5 text-status-warning" />} + </span> + {qn && <span className="shrink-0 font-semibold text-status-warning-on-tint">{qn}</span>} + <span className="min-w-0 truncate">{[qn ? '' : 'Approval', who, count > 1 ? `${count} questions` : count === 1 ? '1 question' : '', relativeAge(item.createdAt, now)].filter(Boolean).join(' · ')}</span> + {open && <span className="ml-auto shrink-0 rounded-full bg-surface-tertiary px-2 py-px text-[12px] font-medium text-text-secondary" data-testid="session-approval-card-open-label">open</span>} + </div> + <p className="mt-1 line-clamp-2 text-[15px] font-semibold leading-snug text-text-primary" data-testid="session-approval-card-title">{item.title}</p> + {item.context && <p className="line-clamp-2 text-[13.5px] leading-snug text-text-secondary" data-testid="session-approval-card-subtitle">{item.context}</p>} + <div className="mt-2 flex items-center gap-1.5 text-[13px]"> + <button type="button" className={stepButton} aria-label="Previous approval" disabled={total < 2} onClick={() => onStep(-1)} data-testid="session-approval-card-prev"><ChevronLeft className="h-3.5 w-3.5" aria-hidden="true" /></button> + <span className="tabular-nums text-text-tertiary" data-testid="session-approval-card-pager"><span className="font-semibold text-text-primary">{index + 1}</span> of {total}</span> + <button type="button" className={stepButton} aria-label="Next approval" disabled={total < 2} onClick={() => onStep(1)} data-testid="session-approval-card-next"><ChevronRight className="h-3.5 w-3.5" aria-hidden="true" /></button> + {total > 1 && ( + <span className="ml-1 flex items-center gap-1" aria-hidden="true"> + {Array.from({ length: Math.min(total, 8) }, (_, at) => ( + <span key={at} className="flex h-2 w-2 items-center justify-center"><span className={cn('h-1.5 w-1.5 rounded-full', at === index ? 'bg-interactive' : 'bg-border-hover')} /></span> + ))} + </span> + )} + <span className="ml-auto flex min-w-0 items-center gap-1 text-text-tertiary"> + {references && <span className="truncate">{references} ·</span>} + <button type="button" className="shrink-0 hover:text-text-primary hover:underline" onClick={() => onOpen('summary')} data-testid="session-approval-card-summary">Summary</button> + </span> + {!open && ( + <button type="button" onClick={() => onOpen()} + className="ml-2 inline-flex h-7 shrink-0 items-center gap-0.5 rounded-lg bg-interactive px-3 text-[13px] font-medium text-text-on-interactive hover:bg-interactive-hover" data-testid="session-approval-card-open"> + Open <ChevronRight className="h-3.5 w-3.5" aria-hidden="true" /> + </button> + )} </div> - <p className="mt-1 text-[15px] font-semibold leading-snug text-text-primary">{item.title}</p> - {item.context && <p className="truncate text-[13.5px] text-text-secondary">{item.context}</p>} - {buttons.length > 0 && ( - <div className="mt-2 flex flex-wrap gap-1.5"> - {buttons.map((choice) => ( - <button key={choice.label} type="button" onClick={() => runNotify({ type: 'answer', id: item.id, answer: { picks: [choice.label] } })} - className="rounded-lg border border-border-secondary px-2.5 py-1 text-[13px] font-semibold text-text-primary hover:bg-surface-hover" data-testid="session-approval-card-choice"> - {choice.label}{choice.recommended ? ' · recommended' : ''} - </button> - ))} - </div> + </div> + ); +} + +const LINE_TONE = { + muted: 'bg-surface-tertiary text-text-secondary', + warning: 'bg-status-warning/10 text-status-warning-on-tint', + info: 'bg-status-info/10 text-status-info-on-tint', + success: 'bg-status-success/10 text-status-success-on-tint', +} satisfies Record<ApprovalStatus['tone'], string>; + +/** + * After a Send (D-344): who is reading the reply, then how it was taken, with Reopen, for the + * Session's approval replied to last. Only here, on that approval and its row: never on another. + */ +function ReplyStatusLine({ sessionId }: { sessionId: string }) { + const items = useNotifyStore(selectNotifyItems); + const [dismissed, setDismissed] = useState<ReadonlySet<string>>(() => new Set()); + const item = useMemo(() => latestReplied(items, sessionId, dismissed), [items, sessionId, dismissed]); + const status = item ? approvalStatus(item, true) : null; + if (!item || !status) return null; + const Icon = status.kind === 'reading' ? Loader2 : status.kind === 'taken' || status.kind === 'sent' ? Check : status.kind === 'ready-again' ? Sparkles : CircleAlert; + const fix = status.kind === 'taken' && !item.reply?.fixed ? { as: 'question' as const, label: 'It was a question' } : status.kind === 'asking' && !item.reply?.fixed ? { as: 'answer' as const, label: 'It was my answer' } : undefined; + const dismiss = () => setDismissed((current) => new Set([...current, item.reply?.id ?? item.id])); + return ( + <div className={cn('flex items-center gap-2 rounded-xl px-3.5 py-2 text-[13.5px]', LINE_TONE[status.tone])} role="status" data-testid="session-approval-status" data-kind={status.kind} data-item-id={item.id}> + <span className="flex h-3.5 w-3.5 shrink-0 items-center justify-center" aria-hidden="true"> + <Icon className={cn('h-3.5 w-3.5', status.kind === 'reading' && 'animate-spin')} /> + </span> + <span className="min-w-0 flex-1 truncate">{status.text}{status.kind === 'reading' ? '…' : ''}</span> + {fix && ( + <button type="button" className="shrink-0 font-medium text-interactive hover:underline" onClick={() => runNotify({ type: 'fix-reply', id: item.id, as: fix.as })} data-testid="session-approval-status-fix">{fix.label}</button> )} - <div className="mt-2 flex flex-wrap items-center gap-2"> - {chip && <ApprovalChip chip={chip} />} - <button type="button" onClick={() => openSessionApproval(sessionId, item.id)} - className="ml-auto inline-flex shrink-0 items-center gap-0.5 text-[13.5px] font-medium text-interactive hover:underline" data-testid="session-approval-card-open"> - Open on the right <ChevronRight className="h-3.5 w-3.5" aria-hidden="true" /> + {(item.state === 'waiting' || item.state === 'answered') && ( + <button type="button" className="shrink-0 font-medium text-interactive hover:underline" data-testid="session-approval-status-reopen" + // Opens it on the right as it stands now: waiting, or answered with its Override. + onClick={() => openSessionApproval(sessionId, item.id)}> + Reopen </button> - </div> + )} + {(status.kind === 'taken' || status.kind === 'sent') && ( + <button type="button" aria-label="Dismiss" onClick={dismiss} className="shrink-0 text-text-tertiary hover:text-text-primary"><X className="h-3.5 w-3.5" aria-hidden="true" /></button> + )} </div> ); } diff --git a/frontend/src/components/approvals/SessionApprovals.tsx b/frontend/src/components/approvals/SessionApprovals.tsx index fed615af8..3897b6735 100644 --- a/frontend/src/components/approvals/SessionApprovals.tsx +++ b/frontend/src/components/approvals/SessionApprovals.tsx @@ -60,6 +60,7 @@ export function SessionApprovalColumn({ sessionId, sessionName, inChat, closeLab }) { const surface = `session:${sessionId}` as const; const open = useApprovalColumnStore((state) => state.open[surface]); + const close = useApprovalColumnStore((state) => state.close); const waiting = useSessionApprovals(sessionId); if (!open) return null; return ( @@ -73,6 +74,8 @@ export function SessionApprovalColumn({ sessionId, sessionName, inChat, closeLab onClose={onClose} inChat={inChat} renderOrigin={(item) => <ApprovalOrigin item={item} />} + // Send closes the column; the chat's status line names who reads the reply (P7). + onSent={() => close(surface)} /> ); } diff --git a/frontend/src/components/approvals/approvalCard.test.ts b/frontend/src/components/approvals/approvalCard.test.ts index 1161071f4..fa91ee825 100644 --- a/frontend/src/components/approvals/approvalCard.test.ts +++ b/frontend/src/components/approvals/approvalCard.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { NotifyItem } from '../../../../shared/types/notify'; -import { canSend, cardChip, cardSummary, cardTabs, dotTone, firstToOpen, markedQuestions, nextAfterSend, readyCount, replyLine, sessionApprovals } from './approvalCard'; +import { bridgedFrom, canSend, cardChip, cardTabs, dockApprovals, dotTone, firstToOpen, latestReplied, markedQuestions, readyCount, recommendedPicks, replyLine, sessionApprovals } from './approvalCard'; const q20: NotifyItem = { id: 'q20', @@ -42,12 +42,12 @@ describe('the approval card', () => { }); it('says how a reply was taken, with its fix until it was used', () => { - expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'reading' } })).toEqual({ tone: 'muted', text: 'Reading your reply…' }); + expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'reading' } })).toEqual({ tone: 'muted', text: 'doozy-overhaul is reading your reply…' }); expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'reading', reader: { kind: 'autopilot', name: 'Autopilot' } } })?.text).toBe('Autopilot is reading your reply…'); expect(replyLine({ ...q20, state: 'answered', reply: { id: 'r', text: 'only if undoable', picks: ['Yes, turn it on', 'One train'], at: 1, state: 'answer' } })).toEqual({ tone: 'success', text: 'Taken as your answer: Yes, turn it on · One train, with your note “only if undoable”', fix: { as: 'question', label: 'It was a question' }, }); - expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'question', reader: { kind: 'asker', name: 'doozy-overhaul' } } })).toMatchObject({ tone: 'warning', text: 'Taken as a question: asking doozy-overhaul. Getting more context…', fix: { as: 'answer' } }); + expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'question', reader: { kind: 'asker', name: 'doozy-overhaul' } } })).toMatchObject({ tone: 'warning', text: 'Taken as a question: doozy-overhaul is getting more context…', fix: { as: 'answer' } }); expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'question', fixed: 'question' } })?.fix).toBeUndefined(); expect(replyLine({ ...q20, reply: { id: 'r', text: 'both', at: 1, state: 'sent', reader: { kind: 'asker', name: 'Claude' } } })).toEqual({ tone: 'success', text: 'Sent to Claude' }); expect(replyLine({ ...q20, reply: { id: 'r', text: 'why?', at: 1, state: 'unread' } })?.text).toMatch(/No agent can read replies right now/); @@ -55,20 +55,32 @@ describe('the approval card', () => { it('chips and pager dots follow the state', () => { expect(cardChip(v2)).toEqual({ tone: 'info', text: 'Updated · ready again' }); - expect(cardChip({ ...q20, reply: { id: 'r', text: 'x', at: 1, state: 'question' } })).toEqual({ tone: 'warning', text: 'Getting more context…' }); + expect(cardChip({ ...q20, reply: { id: 'r', text: 'x', at: 1, state: 'question' } })).toEqual({ tone: 'warning', text: 'doozy-overhaul is getting more context' }); expect(dotTone(v2, false)).toBe('info'); expect(dotTone(v2, true)).toBe('current'); expect(dotTone({ ...q20, reply: { id: 'r', text: 'x', at: 1, state: 'reading' } }, false)).toBe('warning'); }); - it('a send moves on past what is being read', () => { - const items = new Map<string, NotifyItem>([ - ['a', { ...q20, id: 'a' }], - ['b', { ...q20, id: 'b', reply: { id: 'r', text: 'x', at: 1, state: 'reading' } }], - ['c', { ...q20, id: 'c' }], - ]); - expect(nextAfterSend(['a', 'b', 'c'], 'a', items)).toBe('c'); - expect(nextAfterSend(['a', 'b'], 'a', items)).toBeUndefined(); + it('P6: the dock pages past what is being read until it is ready again, and leaves out the chat\'s own question (P5)', () => { + const reading = { ...q20, id: 'b', reply: { id: 'r', text: 'x', at: 1, state: 'reading' as const } }; + const ready = { ...reading, id: 'c', readyAgain: 5 }; + const own = { ...q20, id: 'd', replyIsAnswer: true, source: { ...q20.source, panelId: 'chat-1' } }; + const posted = { ...q20, id: 'e', source: { ...q20.source, panelId: 'chat-1' } }; + expect(dockApprovals([{ ...q20, id: 'a' }, reading, ready, own, posted], 'chat-1').map(item => item.id)).toEqual(['a', 'c', 'e']); + expect(bridgedFrom(own, 'chat-1')).toBe(true); + expect(bridgedFrom(own, 'other-chat')).toBe(false); + }); + + it('P7: the status line follows the reply sent last in the Session, until it is dismissed', () => { + const reading = { ...q20, id: 'b', reply: { id: 'r1', text: 'x', at: 10, state: 'reading' as const } }; + const taken = { ...q20, id: 'c', state: 'answered' as const, reply: { id: 'r2', text: 'y', at: 20, state: 'answer' as const } }; + expect(latestReplied([reading, taken], 's1', new Set())?.id).toBe('c'); + expect(latestReplied([reading, taken], 's1', new Set(['r2']))?.id).toBe('b'); + expect(latestReplied([reading, taken], 'other', new Set())).toBeUndefined(); + }); + + it('recommended picks start selected', () => { + expect(recommendedPicks(q20)).toEqual(['', 'One train']); }); it('Send needs a pick per question, or something typed', () => { @@ -79,21 +91,10 @@ describe('the approval card', () => { }); it('J13: tabs are Q&A, Summary, each reference, then the origin unless you are in it', () => { - expect(cardTabs(q20).map(tab => tab.label)).toEqual(['Questions & answers', 'Summary', 'doozy-overhaul status', 'PR #2480', "doozy-overhaul's tab"]); + expect(cardTabs(q20).map(tab => tab.label)).toEqual(['Decide', 'Summary', 'doozy-overhaul status', 'PR #2480', "doozy-overhaul's tab"]); expect(cardTabs(q20, 'w1').map(tab => tab.kind)).toEqual(['qa', 'summary', 'link', 'link']); }); - it('D-225: the Summary is built from the item, with every round', () => { - const summary = cardSummary(v2); - expect(summary.why).toBe(q20.context); - expect(summary.recommendation).toEqual([{ question: 'One train?', label: 'All in one train' }]); - expect(summary.references.map(reference => reference.kind)).toEqual(['file', 'url', 'origin']); - expect(summary.history).toEqual([ - { version: 1, at: 1_000, whatChanged: [] }, - { version: 2, at: 2_000, asked: 'Can the switch be turned off again?', whatChanged: v2.revision?.whatChanged, by: 'doozy-overhaul' }, - ]); - }); - it('J15: a Session count opens its newest ready-again approval, else its first waiting one', () => { const older = { ...q20, id: 'older', createdAt: 1 }; const ready = { ...v2, id: 'ready', createdAt: 5 }; diff --git a/frontend/src/components/approvals/approvalCard.ts b/frontend/src/components/approvals/approvalCard.ts index 088004d16..af6c63ecd 100644 --- a/frontend/src/components/approvals/approvalCard.ts +++ b/frontend/src/components/approvals/approvalCard.ts @@ -1,4 +1,5 @@ import type { NotifyChoice, NotifyItem, NotifyLink, NotifySubQuestion, NotifyVersion } from '../../../../shared/types/notify'; +import { approvalStatus, replyReader, sentenceStart } from '../../../../shared/approvalStatus'; /** * The approval column's logic (P3 of the approval card), kept apart from its rendering: the @@ -9,6 +10,8 @@ import type { NotifyChoice, NotifyItem, NotifyLink, NotifySubQuestion, NotifyVer /** One question as the column shows it: an options approval's own choices count as one. */ export interface CardQuestion { title: string; + /** Why this question matters, shown under it. */ + why?: string; choices: CardChoice[]; /** Choices the version before had and this one dropped: shown struck out, once. */ removed: string[]; @@ -41,7 +44,8 @@ export function previousVersion(item: NotifyItem): NotifyVersion | undefined { export function markedQuestions(item: NotifyItem): CardQuestion[] { const current = questionsOf(item); const before = previousVersion(item); - if (!before) return current.map(question => ({ title: question.title, choices: question.choices.map(choice => ({ ...choice })), removed: [] })); + const withWhy = (question: NotifySubQuestion, card: Omit<CardQuestion, 'why'>): CardQuestion => (question.why ? { ...card, why: question.why } : card); + if (!before) return current.map(question => withWhy(question, { title: question.title, choices: question.choices.map(choice => ({ ...choice })), removed: [] })); const earlier = questionsOf({ ui: item.ui, title: before.title, choices: before.choices, questions: before.questions }); return current.map((question, index) => { const old = earlier[index]?.choices ?? []; @@ -59,7 +63,7 @@ export function markedQuestions(item: NotifyItem): CardQuestion[] { }); const removed: string[] = []; for (const choice of old) if (!used.has(choice.label)) removed.push(choice.label); - return { title: question.title, choices, removed }; + return withWhy(question, { title: question.title, choices, removed }); }); } @@ -74,18 +78,18 @@ export interface ReplyLine { export function replyLine(item: NotifyItem): ReplyLine | null { const reply = item.reply; if (!reply) return null; - const reader = reply.reader?.name ?? item.source.name; + const status = approvalStatus(item); switch (reply.state) { case 'reading': - return { tone: 'muted', text: reply.reader?.kind === 'autopilot' ? 'Autopilot is reading your reply…' : 'Reading your reply…' }; + return { tone: 'muted', text: `${status?.text ?? `${sentenceStart(replyReader(item))} is reading your reply`}…` }; case 'answer': { - const line: ReplyLine = { tone: 'success', text: `Taken as your answer${reply.picks?.length ? `: ${reply.picks.map(pick => pick || '—').join(' · ')}, with your note “${reply.text}”` : `: “${reply.text}”`}` }; + const line: ReplyLine = { tone: 'success', text: `Taken as your answer${reply.picks?.some(Boolean) ? `: ${reply.picks.map(pick => pick || '—').join(' · ')}, with your note “${reply.text}”` : `: “${reply.text}”`}` }; // A fix is one click, once. if (!reply.fixed) line.fix = { as: 'question', label: 'It was a question' }; return line; } case 'question': { - const line: ReplyLine = { tone: 'warning', text: `Taken as a question: asking ${reader}. ${reply.status ?? 'Getting more context…'}` }; + const line: ReplyLine = { tone: 'warning', text: `Taken as a question: ${status?.text ?? `${sentenceStart(replyReader(item))} is getting more context`}${status?.text.includes(':') ? '' : '…'}` }; if (!reply.fixed) line.fix = { as: 'answer', label: 'It was my answer' }; return line; } @@ -103,8 +107,9 @@ export function cardChip(item: NotifyItem): CardChip { if (item.state === 'answered') return { tone: 'success', text: 'Answered' }; if (item.state !== 'waiting') return null; if (item.readyAgain !== undefined) return { tone: 'info', text: 'Updated · ready again' }; - if (item.reply?.state === 'question') return { tone: 'warning', text: item.reply.status ?? 'Getting more context…' }; - if (item.reply?.state === 'reading') return { tone: 'muted', text: 'Reading your reply…' }; + // The reader by name, for every kind of reader (P7). + if (item.reply?.state === 'question') return { tone: 'warning', text: `${sentenceStart(replyReader(item))} is getting more context` }; + if (item.reply?.state === 'reading') return { tone: 'muted', text: `${sentenceStart(replyReader(item))} is reading your reply` }; return null; } @@ -127,17 +132,6 @@ export function dotTone(item: NotifyItem | undefined, current: boolean): 'curren return 'muted'; } -/** After a send, the next approval to open: the next one along that isn't being read, else none. */ -export function nextAfterSend(order: readonly string[], sentId: string, items: ReadonlyMap<string, NotifyItem>): string | undefined { - const start = order.indexOf(sentId); - for (let step = 1; step < order.length; step += 1) { - const id = order[(Math.max(start, 0) + step) % order.length]; - const item = items.get(id); - if (item && item.state === 'waiting' && !isBeingRead(item) && id !== sentId) return id; - } - return undefined; -} - /** * Whether Send can go: an answer needs one pick per question; anything typed is a reply. A * permission prompt takes only its options. @@ -160,7 +154,7 @@ export type CardTab = /** `inChat`: the panel the person is already looking at; the origin tab is left out when it's that one. */ export function cardTabs(item: NotifyItem, inChat?: string): CardTab[] { const tabs: CardTab[] = [ - { kind: 'qa', key: 'qa', label: 'Questions & answers' }, + { kind: 'qa', key: 'qa', label: 'Decide' }, { kind: 'summary', key: 'summary', label: 'Summary' }, ]; (item.links ?? []).forEach((link, index) => tabs.push({ kind: 'link', key: `link:${index}:${link.label}`, label: link.label, link, index })); @@ -185,45 +179,6 @@ export function askedBy(item: NotifyItem): string[] { return parts; } -/** The Summary (D-225), written by Pane from the approval's own fields, the same way every time. */ -export interface CardSummary { - why?: string; - recommendation: Array<{ question?: string; label: string; detail?: string }>; - questions: NotifySubQuestion[]; - references: Array<{ label: string; kind: 'file' | 'url' | 'approval' | 'origin'; detail: string }>; - history: Array<{ version: number; at: number; asked?: string; whatChanged: string[]; by?: string }>; -} - -export function cardSummary(item: NotifyItem): CardSummary { - const questions = questionsOf(item); - const recommendation: CardSummary['recommendation'] = []; - for (const question of questions) { - for (const choice of question.choices) { - if (!choice.recommended) continue; - const recommended: CardSummary['recommendation'][number] = { label: choice.label }; - if (questions.length > 1) recommended.question = question.title; - if (choice.detail) recommended.detail = choice.detail; - recommendation.push(recommended); - } - } - const references: CardSummary['references'] = (item.links ?? []).map(link => ( - 'path' in link ? { label: link.label, kind: 'file' as const, detail: link.path } - : 'url' in link ? { label: link.label, kind: 'url' as const, detail: link.url } - : { label: link.label, kind: 'approval' as const, detail: 'another approval' } - )); - if (originPanel(item)) references.push({ label: `${item.source.name}'s tab`, kind: 'origin', detail: 'where it was asked' }); - const round = (version: number, at: number, revision: NotifyItem['revision']): CardSummary['history'][number] => ( - revision ? { version, at, asked: revision.asked, whatChanged: revision.whatChanged, by: revision.by } : { version, at, whatChanged: [] } - ); - const history = [ - ...(item.versions ?? []).map(version => round(version.version, version.at, version.revision)), - round(item.version ?? 1, item.revision?.at ?? item.createdAt, item.revision), - ]; - const summary: CardSummary = { recommendation, questions, references, history }; - if (item.context) summary.why = item.context; - return summary; -} - /** Waiting items connected to a Session (its own, its workers', Autopilot's about it), oldest first. */ export function sessionApprovals(items: readonly NotifyItem[], sessionId: string): NotifyItem[] { return items @@ -242,3 +197,43 @@ export function firstToOpen(items: readonly NotifyItem[]): NotifyItem | undefine const ready = items.filter(item => item.readyAgain !== undefined).sort((a, b) => (b.readyAgain ?? 0) - (a.readyAgain ?? 0)); return ready[0] ?? items.find(item => !isBeingRead(item)) ?? items[0]; } + +/** The picks a fresh card starts with: each question's recommended option, '' where none is. */ +export function recommendedPicks(item: NotifyItem): string[] { + if (item.agentPrompt) return []; + return questionsOf(item).map(question => question.choices.find(choice => choice.recommended)?.label ?? ''); +} + +/** + * The approvals a chat's compact card pages through (P5, P6): waiting on the person, not the + * chat's own bridged question (AskUserQuestion or a permission prompt from that same chat, which + * the chat already shows as Claude's card), and not one an agent is reading a reply to, until it + * comes back ready again. + */ +export function dockApprovals(items: readonly NotifyItem[], chatPanelId?: string): NotifyItem[] { + return items.filter(item => !bridgedFrom(item, chatPanelId) && (!isBeingRead(item) || item.readyAgain !== undefined)); +} + +/** A question the chat tab itself asked (its AskUserQuestion or permission prompt), copied to Approvals. */ +export function bridgedFrom(item: NotifyItem, chatPanelId: string | undefined): boolean { + return chatPanelId !== undefined && item.source.panelId === chatPanelId && Boolean(item.replyIsAnswer || item.optionsOnly || item.agentPrompt); +} + +/** + * The chat's status line after a send (P7, D-344): the Session's approval the person replied to + * most recently, while its reply is being read, asked about or came back ready again, or once it + * was taken as the answer (until the person dismisses it). + */ +export function latestReplied(items: readonly NotifyItem[], sessionId: string, dismissed: ReadonlySet<string>): NotifyItem | undefined { + return items + .filter(item => item.reply && !dismissed.has(item.reply.id) && (item.source.session?.id === sessionId || (item.connected?.kind === 'session' && item.connected.id === sessionId))) + .filter(item => (item.state === 'waiting' && (item.reply?.state === 'reading' || item.reply?.state === 'question' || item.reply?.state === 'unread' || item.readyAgain !== undefined)) + || (item.state === 'answered' && item.reply?.state === 'answer')) + .sort((a, b) => (b.readyAgain ?? b.reply?.at ?? 0) - (a.readyAgain ?? a.reply?.at ?? 0))[0]; +} + +/** "3 references": the card's links. */ +export function referenceCount(item: Pick<NotifyItem, 'links'>): string | undefined { + const count = item.links?.length ?? 0; + return count ? `${count} reference${count === 1 ? '' : 's'}` : undefined; +} diff --git a/frontend/src/components/approvals/approvalGroups.ts b/frontend/src/components/approvals/approvalGroups.ts index 93e0ddd06..f474c1b26 100644 --- a/frontend/src/components/approvals/approvalGroups.ts +++ b/frontend/src/components/approvals/approvalGroups.ts @@ -2,6 +2,7 @@ import type { NotifyConnected, NotifyItem, NotifySession } from '../../../../sha import type { OrchestrationSessionRecord } from '../../../../shared/types/orchestrationSession'; import { decisionLine, originLine, receiptLine } from '../../../../shared/approvalReceipts'; import { countsAsWaiting } from '../../stores/notifyStore'; +import { approvalHolder, type ApprovalHolder } from '../../../../shared/approvalStatus'; export type ApprovalsTab = 'waiting' | 'answered'; @@ -54,6 +55,8 @@ export interface ApprovalGroup { autopilot?: boolean; /** A group of the By project sort. */ project?: boolean; + /** Most recent's groups (P7): waiting on you, or with the agent reading your reply. */ + holder?: ApprovalHolder; items: NotifyItem[]; } @@ -188,7 +191,7 @@ export function groupApprovals(items: NotifyItem[], tab: ApprovalsTab, sort: App const listedAt = tab === 'waiting' ? (item: NotifyItem) => item.createdAt : resolved; // By id on a tie, so items of the same millisecond don't swap as snapshots arrive. const newestFirst = [...(tab === 'waiting' ? live : answeredItems)].sort((a, b) => listedAt(b) - listedAt(a) || a.id.localeCompare(b.id)); - const groups = sort === 'recent' ? [] + const groups = sort === 'recent' ? (tab === 'waiting' ? byHolder(newestFirst) : []) : sort === 'project' ? intoGroups(newestFirst, projectGroup) : sort === 'sidebar' ? inSidebarOrder(intoGroups(newestFirst, sessionGroup), sidebarOrder) : intoGroups(newestFirst, sessionGroup); @@ -203,6 +206,21 @@ export function groupApprovals(items: NotifyItem[], tab: ApprovalsTab, sort: App }; } +/** + * Most recent's Waiting tab, once something is with an agent (P7, D-344): "Waiting on you" first, + * then "With the agent" for approvals whose reply an agent is reading or asking about. Flat (no + * groups) while everything waits on you. + */ +function byHolder(items: NotifyItem[]): ApprovalGroup[] { + const withAgent = items.filter(item => approvalHolder(item) === 'agent'); + if (withAgent.length === 0) return []; + const onYou = items.filter(item => approvalHolder(item) !== 'agent'); + const groups: ApprovalGroup[] = []; + if (onYou.length) groups.push({ key: 'holder:you', label: 'Waiting on you', holder: 'you', items: onYou }); + groups.push({ key: 'holder:agent', label: 'With the agent', holder: 'agent', items: withAgent }); + return groups; +} + /** * The ids of the items a queue lists, top to bottom: the order the pager steps through, grouped as * the list shows them under every sort. `withStale` adds the Stale group's items after them, in @@ -215,6 +233,7 @@ export function approvalIdsInListOrder(queue: Pick<ApprovalQueue, 'list' | 'stal /** A group header's count: how many of its items still wait on you, or on Answered how many it lists. */ export function groupCountText(group: ApprovalGroup, tab: ApprovalsTab): string { + if (group.holder === 'agent') return `${group.items.length} sent`; return tab === 'waiting' ? `${group.items.filter(countsAsWaiting).length} waiting` : `${group.items.length} answered`; } diff --git a/frontend/src/components/approvals/summaryPage.ts b/frontend/src/components/approvals/summaryPage.ts new file mode 100644 index 000000000..61db59f28 --- /dev/null +++ b/frontend/src/components/approvals/summaryPage.ts @@ -0,0 +1,52 @@ +import type { NotifyItem } from '../../../../shared/types/notify'; +import { boundary, decodeOptionalBoundary } from '../../../../shared/validation/boundaryDecoder'; + +/** An approval's Summary page as the column shows it, or why it can't. */ +export type SummaryRead = + | { kind: 'page'; path: string; html: string } + | { kind: 'template'; html: string } + | { kind: 'failed'; message: string }; + +/** A read that has not answered by then has failed: no spinner stays up. */ +const READ_TIMEOUT_MS = 10_000; +const SUMMARY_CHANNEL = 'notify:summary-page'; +type MachineRead = (machineId: string, channel: string, args: unknown[]) => Promise<{ data: unknown }>; + +const pageSchema = boundary.object({ kind: boundary.literal('page'), path: boundary.string, html: boundary.string }); +const templateSchema = boundary.object({ kind: boundary.literal('template'), html: boundary.string }); +const missingSchema = boundary.object({ kind: boundary.literal('missing'), message: boundary.string }); + +/** The id the holding machine knows the approval by: a saved machine's card is `<machineId>:<its id>`. */ +export function hostItemId(item: Pick<NotifyItem, 'id' | 'machineId'>): string { + const machine = item.machineId; + return machine && machine !== 'local' && item.id.startsWith(`${machine}:`) ? item.id.slice(machine.length + 1) : item.id; +} + +/** + * Reads the approval's Summary from the machine that holds it (`notify:summary-page`), the same + * read the phone makes. Never rejects: a failed read is a state with its reason. + */ +export async function readSummaryPage(item: NotifyItem, theme: 'light' | 'dark'): Promise<SummaryRead> { + // SAFETY: main refuses a channel it doesn't serve, and that refusal is a failed read below. + const read = window.electronAPI.machines.read as MachineRead; + const machine = item.machine ?? 'this machine'; + let timer: ReturnType<typeof setTimeout> | undefined; + const timeout = new Promise<SummaryRead>((resolve) => { + timer = setTimeout(() => resolve({ kind: 'failed', message: `Couldn't reach ${machine} for the Summary. Try again once it's online.` }), READ_TIMEOUT_MS); + }); + try { + const reading = read(item.machineId ?? 'local', SUMMARY_CHANNEL, [hostItemId(item), { theme }]).then(({ data }): SummaryRead => { + const page = decodeOptionalBoundary(data, pageSchema); + if (page) return page; + const template = decodeOptionalBoundary(data, templateSchema); + if (template) return template; + const missing = decodeOptionalBoundary(data, missingSchema); + return { kind: 'failed', message: missing?.message ?? `${machine} sent no Summary for this approval.` }; + }); + return await Promise.race([reading, timeout]); + } catch (error) { + return { kind: 'failed', message: `Couldn't read the Summary from ${machine}: ${error instanceof Error ? error.message : String(error)}` }; + } finally { + clearTimeout(timer); + } +} diff --git a/frontend/src/components/chatTab/ChatTabRoute.tsx b/frontend/src/components/chatTab/ChatTabRoute.tsx index f464ec7e9..2c41b5dfb 100644 --- a/frontend/src/components/chatTab/ChatTabRoute.tsx +++ b/frontend/src/components/chatTab/ChatTabRoute.tsx @@ -52,7 +52,7 @@ export function ChatTabRoute({ panel }: { panel: ToolPanel }) { placeholder={state?.orchestrationSessionId ? 'Ask the Session anything' : undefined} actions={actions} // A Session's orchestrator chat shows its approvals as light cards above the composer (P5). - dock={state?.orchestrationSessionId ? <SessionApprovalCards sessionId={state.orchestrationSessionId} /> : undefined} + dock={state?.orchestrationSessionId ? <SessionApprovalCards sessionId={state.orchestrationSessionId} chatPanelId={panel.id} /> : undefined} pr={pr} />; } diff --git a/frontend/src/stores/approvalColumnStore.ts b/frontend/src/stores/approvalColumnStore.ts index 171d946c6..dadd916d5 100644 --- a/frontend/src/stores/approvalColumnStore.ts +++ b/frontend/src/stores/approvalColumnStore.ts @@ -23,8 +23,6 @@ interface ApprovalDraft { interface ApprovalColumnState { open: Partial<Record<ApprovalSurface, OpenApproval>>; - /** The last approval the person replied to: a quiet line on whichever approval opens next says how it was taken. */ - lastReplied: string | null; /** What's typed and picked on each approval (by `<id>:<version>`), kept until its Send is accepted (F-4). */ drafts: Record<string, ApprovalDraft>; setDraft(key: string, draft: ApprovalDraft | null): void; @@ -37,12 +35,10 @@ interface ApprovalColumnState { setTab(surface: ApprovalSurface, itemId: string, tab: string): void; openNested(surface: ApprovalSurface, itemId: string, nested: string | undefined): void; close(surface: ApprovalSurface): void; - noteReplied(itemId: string | null): void; } export const useApprovalColumnStore = create<ApprovalColumnState>((set, get) => ({ open: {}, - lastReplied: null, drafts: {}, setDraft: (key, draft) => set((state) => { const drafts = { ...state.drafts }; @@ -69,7 +65,6 @@ export const useApprovalColumnStore = create<ApprovalColumnState>((set, get) => delete open[surface]; return { open }; }), - noteReplied: (itemId) => set({ lastReplied: itemId }), })); /** Opens an approval in a Session's column (from its count, an inline card, or a link). */ diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index 6822242ec..890558fbe 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -973,6 +973,8 @@ export class NotifyQueue extends EventEmitter { delete item.questions; } if (update.links) item.links = update.links; + // The asker's new version replaces what Pane tidied: it's no longer "as sent". + if (update.title?.trim() || update.questions || update.choices) delete item.asSent; item.version = (item.version ?? 1) + 1; const by = item.reply?.reader?.name ?? askerName(item); item.revision = { asked: update.asked.trim(), whatChanged: update.whatChanged.split('\n').map(line => line.trim()).filter(Boolean), by, at: now }; diff --git a/shared/approvalStatus.ts b/shared/approvalStatus.ts new file mode 100644 index 000000000..05d7685b5 --- /dev/null +++ b/shared/approvalStatus.ts @@ -0,0 +1,70 @@ +import type { NotifyItem } from './types/notify'; + +/** + * Where an approval stands after the person sends (P7, D-344), in the words every surface uses: + * the chat's status line, the Approvals list, the column and the phone. The reader is named for + * every kind (the asker's tab, the orchestrator that relayed it, Autopilot), never "its reader". + */ + +export type ApprovalHolder = 'you' | 'agent' | 'done'; + +export type ApprovalStatusKind = 'reading' | 'asking' | 'ready-again' | 'taken' | 'sent' | 'unread'; + +export interface ApprovalStatus { + kind: ApprovalStatusKind; + /** One line, such as "okta-sso-account is reading your reply to Q-42". */ + text: string; + tone: 'muted' | 'warning' | 'info' | 'success'; +} + +/** "Q-42", when the title leads with one. */ +export function approvalQn(item: Pick<NotifyItem, 'title'>): string | undefined { + return /^\s*(Q-\d+)\b/u.exec(item.title)?.[1]; +} + +/** "the Doozy orchestrator" opens a sentence as "The Doozy orchestrator"; a tab's name ("okta-sso-account") stays as it is. */ +export function sentenceStart(text: string): string { + return /^the\s/u.test(text) ? `T${text.slice(1)}` : text; +} + +/** Who reads the person's reply, as the approval names it: the reader, else the asker. */ +export function replyReader(item: Pick<NotifyItem, 'reply' | 'source'>): string { + return item.reply?.reader?.name ?? item.source.name; +} + +/** Who holds the approval now: the person, the agent reading their reply, or nobody (done). */ +export function approvalHolder(item: Pick<NotifyItem, 'state' | 'reply' | 'readyAgain'>): ApprovalHolder { + if (item.state !== 'waiting') return 'done'; + if (item.readyAgain !== undefined) return 'you'; + if (item.reply?.state === 'reading' || item.reply?.state === 'question') return 'agent'; + return 'you'; +} + +/** + * The status line after a send, or null while the approval simply waits on the person. `withQn` + * names the approval ("… your reply to Q-42"), for lines shown away from its card. + */ +export function approvalStatus(item: Pick<NotifyItem, 'title' | 'state' | 'reply' | 'readyAgain' | 'source' | 'answerSummary'>, withQn = false): ApprovalStatus | null { + const qn = withQn ? approvalQn(item) : undefined; + const to = qn ? ` to ${qn}` : ''; + const on = qn ? ` on ${qn}` : ''; + const reader = replyReader(item); + if (item.state === 'waiting' && item.readyAgain !== undefined) return { kind: 'ready-again', text: `${qn ? `${qn} · ` : ''}Updated · ready again`, tone: 'info' }; + const reply = item.reply; + if (!reply) return null; + switch (reply.state) { + case 'reading': + return item.state === 'waiting' ? { kind: 'reading', text: `${sentenceStart(reader)} is reading your reply${to}`, tone: 'muted' } : null; + case 'question': + return item.state === 'waiting' ? { kind: 'asking', text: `${sentenceStart(reader)} is getting more context${on}${reply.status && !/^getting more context/iu.test(reply.status) ? `: ${reply.status}` : ''}`, tone: 'warning' } : null; + case 'answer': { + const picked = reply.picks?.filter(Boolean) ?? []; + const what = picked.length ? `${picked.join(' · ')}, with your note` : `“${reply.text}”`; + return { kind: 'taken', text: `Taken as your answer${on}: ${what}`, tone: 'success' }; + } + case 'sent': + return { kind: 'sent', text: `Sent to ${reader}${qn ? ` (${qn})` : ''}`, tone: 'success' }; + case 'unread': + return item.state === 'waiting' ? { kind: 'unread', text: `No agent can read your reply${to} right now. You can still pick an option.`, tone: 'warning' } : null; + } +} From 2588b001910f8b4331a581140fc3c943e6e2d6c6 Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 17:43:34 -0800 Subject: [PATCH 07/13] Phone: subtitles, named readers, the three approval groups, per-question why and the Summary page --- native/README.md | 2 +- native/src/app/_layout.tsx | 2 + .../src/app/approval/summary/[notifyId].tsx | 9 ++ .../features/allMachines/AllMachinesHome.tsx | 14 ++- .../features/allMachines/allMachinesList.ts | 15 +-- .../src/features/approvals/ApprovalGroups.tsx | 31 ++++++- .../features/approvals/ApprovalRowLines.tsx | 29 ++++++ .../src/features/approvals/ApprovalScreen.tsx | 70 ++++++++++---- .../approvals/ApprovalSummaryScreen.tsx | 93 +++++++++++++++++++ .../src/features/approvals/approvals.test.ts | 79 +++++++++++++++- native/src/features/approvals/approvals.ts | 64 ++++++++++--- .../src/features/notify/NotifySheet.test.ts | 17 ++-- native/src/features/notify/NotifySheet.tsx | 7 +- .../features/notify/notifyQuestions.test.ts | 53 ++++++++--- native/src/features/notify/notifyQuestions.ts | 36 +++---- native/src/features/panes/PaneListScreen.tsx | 27 ++++-- 16 files changed, 452 insertions(+), 96 deletions(-) create mode 100644 native/src/app/approval/summary/[notifyId].tsx create mode 100644 native/src/features/approvals/ApprovalRowLines.tsx create mode 100644 native/src/features/approvals/ApprovalSummaryScreen.tsx diff --git a/native/README.md b/native/README.md index 22da4d507..b2fb0e325 100644 --- a/native/README.md +++ b/native/README.md @@ -40,7 +40,7 @@ Settings opens on the current machine, with every other machine under **Switch t ## Approvals -Home lists what waits on you under **Approvals**, newest first. A sort menu, remembered on the device, chooses **Most recent** (flat, the default), **By Session** (the Session's name and its machine's mark, with **Not in a Session** for the rest) or **By project** (with **No project** for the rest); items from hosts older than Approvals land in those last groups, and each group sits by its newest item. Answers still held for Undo stay listed as "Sent: …". A worker's question is an ordinary card with an answer box from the start. A machine that isn't online collapses to one row ("<machine> offline · N waiting · they come back when <machine> is online"), and stale items and the last 24 hours' answered and closed items sit in collapsed **Stale · N** and **Answered · N** groups at the bottom; none of these count. Cards show who asked, what they're connected to and who acted (`shared/approvalReceipts.ts`), with Close, Override and Reopen sent to the machine holding the item. +Home lists what waits on you under **Approvals**, newest first. A sort menu, remembered on the device, chooses **Most recent** (flat, the default), **By Session** (the Session's name and its machine's mark, with **Not in a Session** for the rest) or **By project** (with **No project** for the rest); items from hosts older than Approvals land in those last groups, and each group sits by its newest item. Answers still held for Undo stay listed as "Sent: …". Each question's row shows the card's subtitle (its context, two lines at most) and, after you reply, where the reply stands in the words every surface uses (`shared/approvalStatus.ts`: "okta-sso-account is reading your reply", "… is getting more context", "Updated · ready again", "Taken as your answer: …"). An approval whose reply an agent is reading or asking about moves to **With the agent · N** below what waits on you, and out of the counts, until it comes back ready again. A worker's question is an ordinary card with an answer box from the start, with one set of buttons per question, each starting on its recommended choice. A machine that isn't online collapses to one row ("<machine> offline · N waiting · they come back when <machine> is online"), and stale items and today's answered and closed items sit in collapsed **Stale · N** and **Done today · N** groups at the bottom; none of these count. The approval screen's **Summary** opens the approval's Summary page (`notify:summary-page`) in a WebView with JavaScript off that refuses every navigation. Cards show who asked, what they're connected to and who acted (`shared/approvalReceipts.ts`), with Close, Override and Reopen sent to the machine holding the item. Tapping one opens its approval screen (`src/app/approval/[notifyId].tsx`): the card on top with "1 of N" and Next, its related files and pages as chips (tapping one opens it as a tab below), and its answer controls; below it, the Session's tabs and orchestrator chat for an orchestrator's item or a fallback, or the asking Pane's tabs for any other. Type there to ask about the item. An item answered elsewhere steps to the next one, or back home when none is left. A question alert's tap opens the same screen, using the `sessionPaneId` the alert carries when the question can't be read. diff --git a/native/src/app/_layout.tsx b/native/src/app/_layout.tsx index 57d1e2de4..69b37c5c0 100644 --- a/native/src/app/_layout.tsx +++ b/native/src/app/_layout.tsx @@ -110,6 +110,8 @@ function RootStack({ signedIn }: { signedIn: boolean }) { <Stack.Screen name="session/[sessionId]" options={{ headerShown: false }} /> {/* The card above its Session's tabs; draws its own bar. */} <Stack.Screen name="approval/[notifyId]" options={{ headerShown: false }} /> + {/* An approval's Summary page (P4), full screen with its own bar. */} + <Stack.Screen name="approval/summary/[notifyId]" options={{ headerShown: false }} /> {/* Autopilot: its chat, an approval full screen, and a document full screen; each draws its own bar. */} <Stack.Screen name="autopilot/index" options={{ headerShown: false }} /> <Stack.Screen name="autopilot/item/[notifyId]" options={{ headerShown: false }} /> diff --git a/native/src/app/approval/summary/[notifyId].tsx b/native/src/app/approval/summary/[notifyId].tsx new file mode 100644 index 000000000..6764e2b6f --- /dev/null +++ b/native/src/app/approval/summary/[notifyId].tsx @@ -0,0 +1,9 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { ApprovalSummaryScreen } from '@/features/approvals/ApprovalSummaryScreen'; + +/** An approval's Summary page, read from the machine the phone is on. */ +export default function ApprovalSummaryRoute() { + const { notifyId } = useLocalSearchParams<{ notifyId: string }>(); + return <ApprovalSummaryScreen notifyId={notifyId} />; +} diff --git a/native/src/features/allMachines/AllMachinesHome.tsx b/native/src/features/allMachines/AllMachinesHome.tsx index 3dff33858..151f2be37 100644 --- a/native/src/features/allMachines/AllMachinesHome.tsx +++ b/native/src/features/allMachines/AllMachinesHome.tsx @@ -7,6 +7,7 @@ import { originLine } from '@shared/approvalReceipts'; import { ApprovalGroups } from '@/features/approvals/ApprovalGroups'; import { askerLine, questionDetail, type ApprovalList } from '@/features/approvals/approvals'; +import { approvalRowSpoken, ApprovalRowLines } from '@/features/approvals/ApprovalRowLines'; import { openApproval } from '@/features/approvals/ApprovalScreen'; import { WaitingCount } from '@/features/approvals/WaitingCount'; import { MachineBadge } from '@/features/machines/MachineBadge'; @@ -126,10 +127,12 @@ export function AllMachinesHome({ machines, feeds, currentKey, loading, refreshi testID={row.type === 'question' ? `notify-row-${row.item.id}` : `permission-row-${row.request.sessionId}`} title={row.type === 'question' ? originLine(row.item, '') : askerLine(row.paneName, row.session)} detail={row.type === 'question' ? questionDetail(row.item) : permission?.target ? `${permission.title}: ${permission.target}` : permission?.title ?? ''} + below={row.type === 'question' ? <ApprovalRowLines item={row.item} testID={`notify-row-${row.item.id}`} /> : undefined} + spoken={row.type === 'question' ? approvalRowSpoken(row.item) : undefined} machine={machine} stale={row.stale} divided={divided} - leading={<StatusBadge status={list === 'answered' ? 'idle' : 'blocked'} />} + leading={<StatusBadge status={list === 'answered' || list === 'agent' ? 'idle' : 'blocked'} />} onPress={() => openWaiting(row, list)} /> {row.type === 'question' && row.item.state === 'waiting' ? ( @@ -281,9 +284,13 @@ function OfflineNote({ machine, since, hasRows, onRetry }: { machine: Machine | } /** A row of All machines: status, name, what it is, and its machine's badge. */ -function AllRow({ title, detail, label, machine, stale, paused = false, divided = false, leading, trailing, onPress, swipe, testID }: { +function AllRow({ title, detail, below, spoken = [], label, machine, stale, paused = false, divided = false, leading, trailing, onPress, swipe, testID }: { title: string; detail: string; + /** A question's subtitle and reply status, under its detail. */ + below?: ReactNode; + /** What `below` says, for screen readers. */ + spoken?: string[]; /** Spoken instead of title and detail. */ label?: string; machine: Machine | undefined; @@ -303,7 +310,7 @@ function AllRow({ title, detail, label, machine, stale, paused = false, divided <Pressable testID={testID} accessibilityRole="button" - accessibilityLabel={label ?? [title, paused ? 'Paused' : undefined, detail, machine?.name].filter(Boolean).join(', ')} + accessibilityLabel={label ?? [title, paused ? 'Paused' : undefined, detail, ...spoken, machine?.name].filter(Boolean).join(', ')} accessibilityHint={stale ? `${machine?.name ?? 'Its machine'} is offline` : undefined} {...(swipe ? swipeRowAccessibility(swipe) : {})} onPress={onPress} @@ -318,6 +325,7 @@ function AllRow({ title, detail, label, machine, stale, paused = false, divided <View style={styles.fill}> <Text variant="body" tone={paused ? 'muted' : 'primary'} numberOfLines={1}>{title}</Text> <Text variant="footnote" tone="muted" numberOfLines={1}>{detail}</Text> + {below} </View> {trailing} {machine ? <MachineBadge machine={machine} /> : null} diff --git a/native/src/features/allMachines/allMachinesList.ts b/native/src/features/allMachines/allMachinesList.ts index 74e689d58..165ae6066 100644 --- a/native/src/features/allMachines/allMachinesList.ts +++ b/native/src/features/allMachines/allMachinesList.ts @@ -4,7 +4,7 @@ import type { OrchestrationSessionRecord } from '@shared/types/orchestrationSess import type { TerminalPausedMark } from '@shared/types/panels'; import type { PanePermissionRequest } from '@shared/types/permissions'; -import { answeredItems, approvalAt, approvalItems, sessionKey, staleItems, waitingCounts, type OfflineMachine } from '@/features/approvals/approvals'; +import { answeredItems, approvalAt, approvalItems, isWithAgent, sessionKey, staleItems, waitingCounts, type OfflineMachine } from '@/features/approvals/approvals'; import { paneAgent, paneDisplayStatus, type AgentStatusSnapshot } from '@/features/panes/agentStatus'; import type { ProjectWithPanes } from '@/features/panes/paneList'; @@ -42,8 +42,10 @@ interface WaitingBase extends RowBase { session?: { id: string; name: string }; project?: string; at: number; - /** False for an answer held for Undo: it shows, but doesn't count. */ + /** False for an answer held for Undo, or a reply the agent is reading: it shows, but doesn't count. */ waiting: boolean; + /** The agent is reading the person's reply: listed under With the agent. */ + withAgent?: boolean; } type QuestionRow = WaitingBase & { type: 'question'; item: NotifyItem }; @@ -82,7 +84,7 @@ export interface AllMachinesList { waiting: WaitingRow[]; /** Online machines' stale questions, oldest first: a collapsed group, out of the counts. */ staleQuestions: QuestionRow[]; - /** Online machines' answered and closed questions of the last 24 hours, newest first: a collapsed group, out of the counts. */ + /** Online machines' answered and closed questions of today, newest first: a collapsed group, out of the counts. */ answered: QuestionRow[]; /** One row per machine that isn't online and had something waiting, instead of its cards. */ offline: OfflineMachine[]; @@ -114,7 +116,7 @@ export function buildAllMachinesList(machines: readonly MachineFeed[], query: st const base = (kind: string, id: string): RowBase => ({ key: `${machine.key}:${kind}:${id}`, machineKey: machine.key, stale: machine.offlineSince !== undefined }); const pending = snapshot.permissions ?? []; const asked = approvalItems(snapshot.notify); - const blocked = new Set([...pending.map(request => request.sessionId), ...asked.flatMap(item => (item.state === 'waiting' && item.source.paneId) || [])]); + const blocked = new Set([...pending.map(request => request.sessionId), ...asked.flatMap(item => (item.state === 'waiting' && !isWithAgent(item) && item.source.paneId) || [])]); const sessions = (snapshot.sessions ?? []).filter(session => !session.archived); const sessionOf = new Map(sessions.flatMap(session => session.associations.map(association => [association.paneId, { id: session.id, name: session.name }] as const))); const paneNames = new Map<string, string>(); @@ -151,13 +153,14 @@ export function buildAllMachinesList(machines: readonly MachineFeed[], query: st } // A machine that isn't online shows one row with its count, never cards you can't act on. if (machine.offlineSince !== undefined) { - const count = asked.filter(item => item.state === 'waiting').length + pending.length; + const count = asked.filter(item => item.state === 'waiting' && !isWithAgent(item)).length + pending.length; if (count > 0) offline.push({ key: machine.key, name: machine.name, waiting: count }); continue; } const questionRow = (item: NotifyItem): QuestionRow => { const { session, project } = item.source; - return { ...base('question', item.id), type: 'question', item, at: approvalAt(item), waiting: item.state === 'waiting', ...(session ? { session: { id: session.id, name: session.name } } : {}), ...(project ? { project } : {}) }; + const withAgent = isWithAgent(item); + return { ...base('question', item.id), type: 'question', item, at: approvalAt(item), waiting: item.state === 'waiting' && !withAgent, ...(withAgent ? { withAgent } : {}), ...(session ? { session: { id: session.id, name: session.name } } : {}), ...(project ? { project } : {}) }; }; questions.push(...asked.map(questionRow)); staleQuestions.push(...staleItems(snapshot.notify).map(questionRow)); diff --git a/native/src/features/approvals/ApprovalGroups.tsx b/native/src/features/approvals/ApprovalGroups.tsx index 2113bd19c..845d6bc9b 100644 --- a/native/src/features/approvals/ApprovalGroups.tsx +++ b/native/src/features/approvals/ApprovalGroups.tsx @@ -13,9 +13,10 @@ import { APPROVAL_SORTS, groupApprovals, machineOfflineLine, waitingCounts, type type MachineOf = (key: string) => Pick<Machine, 'os' | 'slot' | 'state' | 'name'> | undefined; /** - * Home's Approvals: the sort menu, then the entries newest first, flat or in one card per Session or - * project (its name and its machine's mark above it). Then one row per machine that isn't online, and - * the collapsed Stale and Answered groups, none of which count. Each home draws its own rows. + * Home's Approvals: the sort menu, then what waits on you newest first, flat or in one card per + * Session or project (its name and its machine's mark above it). Then With the agent (approvals whose + * reply an agent is reading, P7), one row per machine that isn't online, and the collapsed Stale and + * Done today groups, none of which count. Each home draws its own rows. */ export function ApprovalGroups<T extends ApprovalEntry>({ entries, stale = [], answered = [], offline = [], machineOf, renderEntry }: { entries: readonly T[]; @@ -28,15 +29,26 @@ export function ApprovalGroups<T extends ApprovalEntry>({ entries, stale = [], a }) { const theme = useTheme(); const sort = useApprovalSort(state => state.sort); + const onYou = entries.filter(entry => !entry.withAgent); + const withAgent = entries.filter(entry => entry.withAgent); if (entries.length === 0 && stale.length === 0 && answered.length === 0 && offline.length === 0) return null; return ( <View testID="approvals-section" style={styles.section}> {/* What waits from every Session plus what waits outside one; answers held for Undo don't count. */} <SectionHeader title="Approvals" count={waitingCounts(entries).total} first /> <SortMenu sort={sort} /> - {groupApprovals(entries, sort).map(group => ( + {withAgent.length > 0 && onYou.length > 0 ? <ListLabel testID="approvals-waiting-label" title={`Waiting on you · ${onYou.length}`} /> : null} + {groupApprovals(onYou, sort).map(group => ( <GroupCard key={group.key} group={group} machineOf={machineOf} tint={withAlpha(theme.colors.warning, 0.09)} render={(entry, divided) => renderEntry(entry, divided, 'waiting')} /> ))} + {withAgent.length > 0 ? ( + <View testID="approvals-with-agent" style={styles.group}> + <ListLabel testID="approvals-with-agent-label" title={`With the agent · ${withAgent.length}`} /> + {groupApprovals(withAgent, sort).map(group => ( + <GroupCard key={group.key} group={group} machineOf={machineOf} tint={theme.colors.surfaceRaised} render={(entry, divided) => renderEntry(entry, divided, 'agent')} /> + ))} + </View> + ) : null} {offline.map(machine => { const mark = machineOf(machine.key); return ( @@ -47,7 +59,7 @@ export function ApprovalGroups<T extends ApprovalEntry>({ entries, stale = [], a ); })} <CollapsedGroup testID="approvals-stale" title="Stale" entries={stale} groups={[{ key: 'stale', label: '', entries: [...stale] }]} machineOf={machineOf} render={(entry, divided) => renderEntry(entry, divided, 'stale')} /> - <CollapsedGroup testID="approvals-answered" title="Answered" entries={answered} groups={groupApprovals(answered, sort)} machineOf={machineOf} render={(entry, divided) => renderEntry(entry, divided, 'answered')} /> + <CollapsedGroup testID="approvals-answered" title="Done today" entries={answered} groups={groupApprovals(answered, sort)} machineOf={machineOf} render={(entry, divided) => renderEntry(entry, divided, 'answered')} /> </View> ); } @@ -79,6 +91,15 @@ function SortMenu({ sort }: { sort: ApprovalSort }) { ); } +/** A list's name over its cards: "Waiting on you · 2", "With the agent · 1". */ +function ListLabel({ title, testID }: { title: string; testID: string }) { + return ( + <View testID={testID} style={styles.groupHeader} accessible accessibilityRole="header"> + <Text variant="footnote" tone="secondary" numberOfLines={1} style={styles.groupLabel}>{title}</Text> + </View> + ); +} + /** One group's card, with its header unless it's a flat list's. */ function GroupCard<T extends ApprovalEntry>({ group, machineOf, tint, render }: { group: ApprovalGroup<T>; machineOf: MachineOf; tint: string; render: (entry: T, divided: boolean) => ReactNode }) { const theme = useTheme(); diff --git a/native/src/features/approvals/ApprovalRowLines.tsx b/native/src/features/approvals/ApprovalRowLines.tsx new file mode 100644 index 000000000..fd5c2ee90 --- /dev/null +++ b/native/src/features/approvals/ApprovalRowLines.tsx @@ -0,0 +1,29 @@ +import type { NotifyItem } from '@shared/types/notify'; + +import { replyStateLine, type ReplyTone } from '@/features/notify/notifyQuestions'; +import { useTheme } from '@/theme'; +import { Text } from '@/ui'; + +import { questionSubtitle } from './approvals'; + +/** What a question's row says beyond its title, for screen readers: its subtitle and reply status. */ +export function approvalRowSpoken(item: NotifyItem): string[] { + return [questionSubtitle(item), replyStateLine(item)?.text].filter(line => line !== undefined); +} + +/** + * Under a question's row on home: the card's subtitle (its context, two lines at most) and, + * after the person replied, where the reply stands ("okta-sso-account is reading your reply"). + */ +export function ApprovalRowLines({ item, testID }: { item: NotifyItem; testID: string }) { + const theme = useTheme(); + const subtitle = questionSubtitle(item); + const status = replyStateLine(item); + const color: Record<ReplyTone, string> = { success: theme.colors.success, warning: theme.colors.warning, accent: theme.colors.accentText, muted: theme.colors.textSecondary }; + return ( + <> + {subtitle ? <Text testID={`${testID}-subtitle`} variant="footnote" tone="secondary" numberOfLines={2}>{subtitle}</Text> : null} + {status ? <Text testID={`${testID}-status`} variant="footnote" numberOfLines={1} style={{ color: color[status.tone] }}>{status.text}</Text> : null} + </> + ); +} diff --git a/native/src/features/approvals/ApprovalScreen.tsx b/native/src/features/approvals/ApprovalScreen.tsx index c29e99a8c..5ba809e22 100644 --- a/native/src/features/approvals/ApprovalScreen.tsx +++ b/native/src/features/approvals/ApprovalScreen.tsx @@ -26,8 +26,9 @@ import { useTheme } from '@/theme'; import { Button, ErrorState, Icon, Text } from '@/ui'; import { ApprovalActions, ConnectedRow, QuestionActions } from './ApprovalActions'; +import { openApprovalSummary } from './ApprovalSummaryScreen'; import { useApprovalSort } from './approvalSortPreference'; -import { approvalKindLabel, approvalLinkTarget, linkIcon, approvalListOrder, approvalParams, approvalPosition, approvalWorkspace, connectedLink, isResolved, nextApproval, OLDER_MACHINE_NOTE, openApprovalLink, receiptLines, resolveSelection, type ApprovalList, type ApprovalWorkspace } from './approvals'; +import { approvalKindLabel, approvalLinkTarget, linkIcon, approvalListOrder, approvalParams, approvalPosition, approvalWorkspace, connectedLink, isResolved, nextApproval, OLDER_MACHINE_NOTE, openApprovalLink, receiptLines, resolveSelection, summaryItemId, type ApprovalList, type ApprovalWorkspace } from './approvals'; /** The card never takes more than this share of the screen; the Session below keeps the rest. */ const CARD_MAX_SHARE = 0.45; @@ -86,25 +87,52 @@ export function ApprovalScreen({ notifyId, hintPaneId, list = 'waiting' }: { not return <Workspace workspace={workspace} topBar={topBar} card={card} quiet={!item || finished} />; } -/** The card above the workspace: a Session's (with its chat), a Pane's tabs, or nothing for a question asked outside Pane. */ -const CONTEXT_LINES = 4; +/** + * The card's title and subtitle (its context, in full). A card Pane tidied (P8) says so, and Show + * as sent swaps in the title and context exactly as the asker sent them. + */ +function CardHeading({ item, collapsed }: { item: NotifyItem; collapsed: boolean }) { + const [asSent, setAsSent] = useState(false); + const sent = asSent ? item.asSent : undefined; + const title = sent ? sent.title : item.title; + const context = sent ? sent.context : item.context; + return ( + <> + <Text testID="notify-title" variant="headline" selectable numberOfLines={collapsed ? 1 : undefined}>{title}</Text> + {collapsed ? null : ( + <> + {context ? <Text testID="approval-context" variant="subhead" tone="secondary" selectable>{context}</Text> : null} + {item.asSent ? ( + <View style={styles.tidied}> + <Text testID="approval-tidied" variant="footnote" tone="muted">Tidied by Pane</Text> + <Pressable testID="approval-show-as-sent" accessibilityRole="button" accessibilityState={{ selected: asSent }} hitSlop={8} onPress={() => setAsSent(!asSent)}> + <Text variant="footnote" tone="accent">{asSent ? 'Show tidied' : 'Show as sent'}</Text> + </Pressable> + </View> + ) : null} + </> + )} + </> + ); +} -/** A card's context, four lines at most until Show more, so the answers stay in view. */ -function ClampedContext({ text }: { text: string }) { - const [open, setOpen] = useState(false); - const [clamped, setClamped] = useState(false); +/** Opens the approval's Summary page; only an approval of the machine the phone is on has one here. */ +function SummaryRow({ item }: { item: NotifyItem }) { + const theme = useTheme(); + const id = summaryItemId(item); + if (!id) return null; return ( - <View> - <Text testID="approval-context" variant="subhead" tone="secondary" selectable numberOfLines={open ? undefined : CONTEXT_LINES} - onTextLayout={event => { if (!open) setClamped(event.nativeEvent.lines.length >= CONTEXT_LINES); }}> - {text} - </Text> - {clamped || open ? ( - <Pressable testID="approval-context-more" accessibilityRole="button" hitSlop={8} onPress={() => setOpen(!open)}> - <Text variant="footnote" tone="secondary">{open ? 'Show less' : 'Show more'}</Text> - </Pressable> - ) : null} - </View> + <Pressable + testID="approval-summary" + accessibilityRole="button" + accessibilityLabel="Open the Summary" + onPress={() => openApprovalSummary(id)} + style={({ pressed }) => [styles.chip, styles.summary, { borderRadius: theme.radius.md, backgroundColor: pressed ? theme.colors.surfacePressed : theme.colors.surfaceRaised }]} + > + <Icon ios="doc.richtext" android="article" size={14} color={theme.colors.textSecondary} /> + <Text variant="subhead" numberOfLines={1} style={styles.chipLabel}>Summary</Text> + <Icon ios="chevron.right" android="chevron_right" size={12} color={theme.colors.textMuted} /> + </Pressable> ); } @@ -284,13 +312,13 @@ function ApprovalCard({ item, shared, onNext }: { item: NotifyItem; shared: Noti <Icon ios={collapsed ? 'chevron.down' : 'chevron.up'} android={collapsed ? 'expand_more' : 'expand_less'} size={14} color={theme.colors.textMuted} /> </Pressable> <Text testID="approval-origin" variant="footnote" tone="muted" numberOfLines={1}>{originLine(item, profile.label)}</Text> - <Text testID="notify-title" variant="headline" selectable numberOfLines={collapsed ? 1 : undefined}>{item.title}</Text> + <CardHeading item={item} collapsed={collapsed} /> {collapsed ? null : ( <> - {item.context ? <ClampedContext text={item.context} /> : null} <RevisionLines item={item} /> <ConnectedRow link={connectedLink(item, profile.label)} /> <LinkChips item={item} shared={shared} shownPaneId={shownPaneId} /> + <SummaryRow item={item} /> {item.relay ? null : <ReplyStatusLines item={item} fixing={fixing} onFix={as => command.mutate([{ type: 'fix-reply', id: item.id, as }])} />} {body} {receiptLines(item).filter(line => state.kind !== 'closed' || line !== state.message).map(line => ( @@ -390,5 +418,7 @@ const styles = StyleSheet.create({ chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, chip: { flexDirection: 'row', alignItems: 'center', gap: 6, minHeight: 32, maxWidth: '100%', paddingHorizontal: 10 }, chipLabel: { flexShrink: 1 }, + summary: { alignSelf: 'flex-start' }, + tidied: { flexDirection: 'row', alignItems: 'center', gap: 10 }, bareNote: { padding: 16, textAlign: 'center' }, }); diff --git a/native/src/features/approvals/ApprovalSummaryScreen.tsx b/native/src/features/approvals/ApprovalSummaryScreen.tsx new file mode 100644 index 000000000..b41f0c816 --- /dev/null +++ b/native/src/features/approvals/ApprovalSummaryScreen.tsx @@ -0,0 +1,93 @@ +import { router } from 'expo-router'; +import { ActivityIndicator, Pressable, StyleSheet, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { WebView } from 'react-native-webview'; + +import type { NotifySummaryPage } from '@shared/types/notify'; + +import { useInvokeQuery } from '@/daemon'; +import { ConnectionBar } from '@/features/hosts/ConnectionStatus'; +import { useConnectionPresence } from '@/features/hosts/useConnectionPresence'; +import { useNotifyShared } from '@/features/notify/hooks'; +import { StateScreen } from '@/features/tabs/StateScreen'; +import { useTheme } from '@/theme'; +import { ErrorState, Icon, Text } from '@/ui'; + +import { summaryLoadAllowed } from './approvals'; + +/** Opens approval `notifyId`'s Summary above its card. `notifyId` is the id on the machine the phone is on. */ +export function openApprovalSummary(notifyId: string) { + router.push({ pathname: '/approval/summary/[notifyId]', params: { notifyId } }); +} + +function back() { + if (router.canGoBack()) router.back(); + else router.replace('/'); +} + +/** + * An approval's Summary (P4): the asker's own page, or the one Pane writes from the card, read + * through the phone's connection (`notify:summary-page`) in the phone's light or dark. The page + * renders with scripts off and goes nowhere: its html carries a CSP with no scripts or network, + * and the WebView also turns JavaScript off, injects nothing and refuses every navigation. + */ +export function ApprovalSummaryScreen({ notifyId }: { notifyId: string }) { + const theme = useTheme(); + const { colors } = theme; + const insets = useSafeAreaInsets(); + const { notice, retry } = useConnectionPresence(); + const title = useNotifyShared().data?.items.find(item => item.id === notifyId)?.title; + const page = useInvokeQuery<NotifySummaryPage>('notify:summary-page', [notifyId, { theme: theme.scheme }], { retry: false }); + + let body; + if (page.isPending) { + body = <View style={styles.center}><ActivityIndicator /></View>; + } else if (page.isError) { + body = <ErrorState title="Couldn’t open the Summary" error={page.error} onRetry={() => void page.refetch()} testID="approval-summary-error" />; + } else if (page.data.kind === 'missing') { + body = <StateScreen testID="approval-summary-missing" icon={{ ios: 'doc.questionmark', android: 'draft' }} title="No Summary to show" message={page.data.message} />; + } else { + body = ( + <WebView + testID="approval-summary-page" + source={{ html: page.data.html }} + // Every URL reaches onShouldStartLoadWithRequest, which refuses all but the page itself. A + // narrower list would hand the URLs it doesn't match to the phone to open (react-native-webview). + originWhitelist={['*']} + onShouldStartLoadWithRequest={request => summaryLoadAllowed(request.url)} + javaScriptEnabled={false} + javaScriptCanOpenWindowsAutomatically={false} + setSupportMultipleWindows={false} + allowFileAccess={false} + allowsLinkPreview={false} + dataDetectorTypes="none" + cacheEnabled={false} + style={[styles.fill, { backgroundColor: colors.surface }]} + /> + ); + } + + return ( + <View testID="approval-summary-screen" style={[styles.fill, { backgroundColor: colors.surface }]}> + <View style={{ paddingTop: insets.top, borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}> + <View style={styles.bar}> + <Pressable testID="approval-summary-back" accessibilityRole="button" accessibilityLabel="Back to the approval" hitSlop={6} onPress={back} style={styles.back}> + <Icon ios="chevron.left" android="arrow_back" size={18} color={colors.text} /> + <Text variant="headline">Summary</Text> + </Pressable> + {title ? <Text variant="subhead" tone="muted" numberOfLines={1} style={styles.name}>{title}</Text> : null} + </View> + </View> + <ConnectionBar notice={notice} onRetry={retry} /> + <View style={[styles.fill, { paddingBottom: insets.bottom }]}>{body}</View> + </View> + ); +} + +const styles = StyleSheet.create({ + fill: { flex: 1, minWidth: 0 }, + center: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + bar: { minHeight: 50, flexDirection: 'row', alignItems: 'center', gap: 10, paddingHorizontal: 12 }, + back: { flexDirection: 'row', alignItems: 'center', gap: 8, minHeight: 44, paddingRight: 8 }, + name: { flex: 1, textAlign: 'right' }, +}); diff --git a/native/src/features/approvals/approvals.test.ts b/native/src/features/approvals/approvals.test.ts index efb818816..bdb4c2e1e 100644 --- a/native/src/features/approvals/approvals.test.ts +++ b/native/src/features/approvals/approvals.test.ts @@ -17,6 +17,7 @@ import { connectedLink, groupApprovals, isStale, + isWithAgent, itemEntry, linkIcon, machineOfflineLine, @@ -32,7 +33,11 @@ import { resolveSelection, sessionKey, sessionsByPane, + questionSubtitle, + startOfDay, staleItems, + summaryItemId, + summaryLoadAllowed, waitingCounts, type ApprovalSort, } from './approvals'; @@ -321,20 +326,28 @@ describe('stale items (D-147)', () => { }); }); -describe('the Answered group', () => { - const now = 100 * 60 * 60 * 1000; +describe('the Done today group', () => { + // 15:00 local, so a few hours back is still today wherever the tests run. + const now = new Date(2026, 10, 11, 15).getTime(); const hoursAgo = (hours: number) => now - hours * 60 * 60 * 1000; const answered = item('answered', 1, { state: 'answered', resolvedAt: hoursAgo(2) }); const closed = item('closed', 2, { state: 'expired', resolvedAt: hoursAgo(1), closed: { by: 'autopilot', reason: 'stale', at: hoursAgo(1) } }); const old = item('old', 3, { state: 'answered', resolvedAt: hoursAgo(25) }); const state = at(answered, closed, old, item('open', 4), item('fyi', 5, { state: 'info' })); - it('lists the last 24 hours\' answered and closed items, newest first', () => { + it('lists today\'s answered and closed items, newest first', () => { expect(answeredItems(state, now).map(entry => entry.id)).toEqual(['closed', 'answered']); expect(approvalListOrder(state, 'answered', now, 'recent')).toEqual(['closed', 'answered']); expect(answeredItems(undefined, now)).toEqual([]); }); + it('starts at local midnight, not 24 hours back', () => { + const lateLastNight = item('late', 6, { state: 'answered', resolvedAt: new Date(2026, 10, 10, 23, 30).getTime() }); + const earlyToday = item('early', 7, { state: 'expired', resolvedAt: new Date(2026, 10, 11, 0, 5).getTime() }); + expect(answeredItems(at(lateLastNight, earlyToday), now).map(entry => entry.id)).toEqual(['early']); + expect(startOfDay(now)).toBe(new Date(2026, 10, 11).getTime()); + }); + it('sorts by when each was answered, asked earlier or not, and groups the same way', () => { const inProject = (project: string) => ({ source: { name: 'x', agent: 'Claude', project } }); const late = item('asked-first', 1, { ...inProject('Pane'), state: 'answered', resolvedAt: hoursAgo(1) }); @@ -351,6 +364,7 @@ describe('the Answered group', () => { it('reads a route\'s list, and anything else as Waiting', () => { expect(parseApprovalList('stale')).toBe('stale'); expect(parseApprovalList('answered')).toBe('answered'); + expect(parseApprovalList('agent')).toBe('agent'); expect(parseApprovalList(undefined)).toBe('waiting'); expect(parseApprovalList('nonsense')).toBe('waiting'); }); @@ -447,3 +461,62 @@ describe('a link to another approval', () => { expect(linkIcon({ label: 'plan.md', path: '/repo/plan.md' }).ios).toBe('doc.text'); }); }); + +describe('Waiting on you and With the agent (P7)', () => { + const reply = (state: 'reading' | 'question' | 'unread', at = 5_000): NotifyItem['reply'] => ({ id: `r-${state}`, text: 'Why?', at, state, reader: { kind: 'asker', name: 'okta-sso-account' } }); + const plain = item('plain', 1_000); + const reading = item('reading', 2_000, { ui: 'options', reply: reply('reading') }); + const asking = item('asking', 3_000, { ui: 'options', reply: reply('question') }); + const unread = item('unread', 4_000, { ui: 'options', reply: reply('unread') }); + const ready = item('ready', 5_000, { ui: 'options', readyAgain: 6_000, version: 2 }); + const state = at(plain, reading, asking, unread, ready, item('held', 7_000, { state: 'sent' })); + + it('moves an approval to With the agent while its reply is read or asked about, and back once it is ready again', () => { + expect([plain, reading, asking, unread, ready].map(isWithAgent)).toEqual([false, true, true, false, false]); + expect(approvalListOrder(state, 'waiting', 0, 'recent')).toEqual(['held', 'ready', 'unread', 'plain']); + expect(approvalListOrder(state, 'agent', 0, 'recent')).toEqual(['asking', 'reading']); + // Answering from the approval screen steps through Waiting on you only. + expect(approvalOrder(state, 'recent')).toEqual(['held', 'ready', 'unread', 'plain']); + }); + + it('leaves the counts while the agent has it, and marks its entry', () => { + const entries = approvalItems(state).map(entry => itemEntry(entry, 'mac')); + expect(entries.filter(entry => entry.withAgent).map(entry => entry.item.id)).toEqual(['reading', 'asking']); + expect(waitingCounts(entries).total).toBe(3); + expect(itemEntry(reading, 'mac')).toMatchObject({ waiting: false, withAgent: true }); + expect(itemEntry(plain, 'mac').withAgent).toBeUndefined(); + }); + + it('keeps each list\'s sort: By project groups With the agent the same way', () => { + const inProject = (id: string, createdAt: number, project: string) => item(id, createdAt, { ui: 'options', reply: reply('reading'), source: { name: 'x', agent: 'Claude', project } }); + const agentState = at(inProject('a', 1, 'Pane'), inProject('b', 2, 'Doozy'), inProject('c', 3, 'Pane')); + expect(approvalListOrder(agentState, 'agent', 0, 'project')).toEqual(['c', 'a', 'b']); + }); + + it('opens a linked approval in whichever list holds it', () => { + expect(approvalLinkTarget(state, 'reading', 0)).toEqual({ id: 'reading', list: 'agent' }); + expect(approvalLinkTarget(state, 'plain', 0)).toEqual({ id: 'plain', list: 'waiting' }); + }); +}); + +describe('the subtitle and the Summary (P4)', () => { + it('shows the card\'s context as a row\'s subtitle, and nothing for a blank one', () => { + expect(questionSubtitle(item('a', 1, { context: ' Okta signs people out of every app at once. ' }))).toBe('Okta signs people out of every app at once.'); + expect(questionSubtitle(item('b', 1, { context: ' ' }))).toBeUndefined(); + expect(questionSubtitle(item('c', 1))).toBeUndefined(); + }); + + it('asks this machine for the Summary of its own approvals only', () => { + expect(summaryItemId(item('q1', 1))).toBe('q1'); + expect(summaryItemId(item('q1', 1, { machineId: 'local' }))).toBe('q1'); + expect(summaryItemId(item('studio:q1', 1, { machineId: 'studio' }))).toBeNull(); + }); + + it('loads only the page it was handed, never a link', () => { + expect(summaryLoadAllowed('about:blank')).toBe(true); + expect(summaryLoadAllowed('https://example.com/')).toBe(false); + expect(summaryLoadAllowed('file:///etc/hosts')).toBe(false); + expect(summaryLoadAllowed('pane://open?pane=1')).toBe(false); + expect(summaryLoadAllowed('about:blank#top')).toBe(false); + }); +}); diff --git a/native/src/features/approvals/approvals.ts b/native/src/features/approvals/approvals.ts index 625fde9c9..d34301b0e 100644 --- a/native/src/features/approvals/approvals.ts +++ b/native/src/features/approvals/approvals.ts @@ -1,4 +1,5 @@ import { decisionLine, receiptLine } from '@shared/approvalReceipts'; +import { approvalHolder } from '@shared/approvalStatus'; import { RemoteUnconfirmedResultError } from '@shared/remoteClient'; import type { NotifyItem, NotifyLink, NotifyRemoteOpenLink, NotifySharedState } from '@shared/types/notify'; @@ -35,8 +36,10 @@ export interface ApprovalEntry { project?: string; /** Epoch ms it sorts by, newest first: when it was asked, or when it was answered once it's done. */ at: number; - /** It waits on you: an open question, a fallback item, or a permission request. False for an answer held for Undo. */ + /** It waits on you: an open question, a fallback item, or a permission request. False for an answer held for Undo, and while the agent reads your reply. */ waiting: boolean; + /** The agent is reading the person's reply or getting more context: listed under With the agent (P7). */ + withAgent?: boolean; } export interface ApprovalGroup<T extends ApprovalEntry> { @@ -129,31 +132,45 @@ export function staleItems(state: NotifySharedState | undefined): NotifyItem[] { return (state?.items ?? []).filter(isStale).sort((a, b) => a.createdAt - b.createdAt); } -/** How long the Answered group lists an item, as the desktop's Answered list does. */ -const ANSWERED_WINDOW_MS = 24 * 60 * 60 * 1000; +/** Local midnight of the day `now` falls on: Done today lists what ended since. */ +export function startOfDay(now: number): number { + const day = new Date(now); + day.setHours(0, 0, 0, 0); + return day.getTime(); +} /** It was answered or closed: its card shows a receipt, and it takes an Override or a Reopen. */ export function isResolved(item: NotifyItem): boolean { return item.state === 'answered' || item.state === 'expired'; } -/** The collapsed Answered group: the last 24 hours' answered and closed items, newest first. */ +/** The collapsed Done today group: today's answered and closed items, newest first. */ export function answeredItems(state: NotifySharedState | undefined, now: number): NotifyItem[] { const resolvedAt = (item: NotifyItem) => item.resolvedAt ?? item.createdAt; - return (state?.items ?? []).filter(item => isResolved(item) && now - resolvedAt(item) <= ANSWERED_WINDOW_MS).sort((a, b) => resolvedAt(b) - resolvedAt(a)); + const since = startOfDay(now); + return (state?.items ?? []).filter(item => isResolved(item) && resolvedAt(item) >= since).sort((a, b) => resolvedAt(b) - resolvedAt(a)); +} + +/** The agent holds it: it is reading the person's reply, or getting more context (With the agent). */ +export function isWithAgent(item: NotifyItem): boolean { + return approvalHolder(item) === 'agent'; } -/** Which of Approvals' lists the approval screen steps through. */ -export type ApprovalList = 'waiting' | 'stale' | 'answered'; +/** + * Which of Approvals' lists the approval screen steps through: Waiting on you, With the agent, + * Stale, or Done today (`answered`). + */ +export type ApprovalList = 'waiting' | 'agent' | 'stale' | 'answered'; export function parseApprovalList(value: string | undefined): ApprovalList { - return value === 'stale' || value === 'answered' ? value : 'waiting'; + return value === 'stale' || value === 'answered' || value === 'agent' ? value : 'waiting'; } /** The ids the approval screen steps through and counts for `list`: the order home shows them in. */ export function approvalListOrder(state: NotifySharedState | undefined, list: ApprovalList, now: number, sort: ApprovalSort): string[] { if (list === 'stale') return staleItems(state).map(item => item.id); - return orderOf(list === 'answered' ? answeredItems(state, now) : approvalItems(state), sort); + if (list === 'answered') return orderOf(answeredItems(state, now), sort); + return orderOf(approvalItems(state).filter(item => isWithAgent(item) === (list === 'agent')), sort); } /** When an item sorts: as it was answered once it's done, else as it was asked. */ @@ -164,7 +181,8 @@ export function approvalAt(item: NotifyItem): number { /** A Notify item as an Approvals entry. */ export function itemEntry(item: NotifyItem, machineKey: string): ApprovalEntry & { item: NotifyItem } { const { session, project } = item.source; - return { key: `${machineKey}:question:${item.id}`, machineKey, at: approvalAt(item), waiting: item.state === 'waiting', item, ...(session ? { session: { id: session.id, name: session.name } } : {}), ...(project ? { project } : {}) }; + const withAgent = isWithAgent(item); + return { key: `${machineKey}:question:${item.id}`, machineKey, at: approvalAt(item), waiting: item.state === 'waiting' && !withAgent, ...(withAgent ? { withAgent } : {}), item, ...(session ? { session: { id: session.id, name: session.name } } : {}), ...(project ? { project } : {}) }; } function orderOf(items: readonly NotifyItem[], sort: ApprovalSort): string[] { @@ -173,7 +191,7 @@ function orderOf(items: readonly NotifyItem[], sort: ApprovalSort): string[] { /** The approval screen's order, the same as home's: item ids group by group. "1 of N" counts these. */ export function approvalOrder(state: NotifySharedState | undefined, sort: ApprovalSort): string[] { - return orderOf(approvalItems(state), sort); + return approvalListOrder(state, 'waiting', 0, sort); } /** "1 of 5": where `id` sits in `order`, or null when it isn't there. */ @@ -305,7 +323,7 @@ export async function openApprovalLink( * Approvals' lists holds it (a Q-n, which only the computer resolves, or an item past a day). */ export function approvalLinkTarget(state: NotifySharedState | undefined, ref: string, now: number): { id: string; list: ApprovalList } | null { - const lists: ApprovalList[] = ['waiting', 'stale', 'answered']; + const lists: ApprovalList[] = ['waiting', 'agent', 'stale', 'answered']; const list = lists.find(candidate => approvalListOrder(state, candidate, now, 'recent').includes(ref)); return list ? { id: ref, list } : null; } @@ -390,3 +408,25 @@ export function questionDetail(item: NotifyItem): string { const receipt = receiptLine(item) ?? item.answerSummary; return receipt ? `${receipt} · ${item.title}` : item.title; } + +/** A row's subtitle: the card's context, which the row shows in up to two lines. Absent when the card has none. */ +export function questionSubtitle(item: NotifyItem): string | undefined { + return item.context?.trim() || undefined; +} + +/** + * The id `notify:summary-page` takes for `item` on the machine the phone is connected to. Only + * that machine's own approvals have a Summary there: one it shows from another saved machine + * (`machineId` set to that machine) is null, and opens from that machine instead. + */ +export function summaryItemId(item: NotifyItem): string | null { + return !item.machineId || item.machineId === 'local' ? item.id : null; +} + +/** + * The Summary's WebView loads only the page it was handed (`about:blank`, how a WebView names + * inline html); every link and redirect is refused, so a Summary never navigates anywhere. + */ +export function summaryLoadAllowed(url: string): boolean { + return url === 'about:blank'; +} diff --git a/native/src/features/notify/NotifySheet.test.ts b/native/src/features/notify/NotifySheet.test.ts index ca87583f9..01c3c333e 100644 --- a/native/src/features/notify/NotifySheet.test.ts +++ b/native/src/features/notify/NotifySheet.test.ts @@ -37,8 +37,8 @@ const { shared, command } = vi.hoisted(() => { const both: NotifyItem = { id: 'q-both', ui: 'options', title: 'Set up trains', state: 'waiting', createdAt: 2_500, source: { paneId: 'pane-1', name: 'doozy-overhaul', agent: 'Claude' }, - questions: [{ title: 'Turn it on?', choices: [{ label: 'Yes' }, { label: 'No' }] }, { title: 'How many?', choices: [{ label: 'One train' }] }], - reply: { id: 'r1', text: 'Why one?', at: 2_600, state: 'question', reader: { kind: 'asker', name: 'doozy-overhaul' } }, + questions: [{ title: 'Turn it on?', why: 'Trains cost money while on.', choices: [{ label: 'Yes' }, { label: 'No', recommended: true }] }, { title: 'How many?', choices: [{ label: 'One train' }] }], + reply: { id: 'r1', text: 'Why one?', at: 2_600, state: 'question', reader: { kind: 'asker', name: 'Doozy' } }, }; const shared: NotifySharedState = { machine: 'Work Mac', epoch: 'e1', revision: 1, enabled: true, queue: ['q-secret', 'q-trains', 'q-both'], hidden: [], items: [secret, trains, both] }; return { shared, command: { isPending: false, isError: false, mutate: vi.fn() } }; @@ -80,7 +80,7 @@ describe('NotifySheet replies (P7)', () => { it('shows a revised approval’s What changed and "Updated · ready again", and marks a reworded choice', () => { const tree = elements(createElement(NotifySheet, { notifyId: 'q-trains' })); - expect(text(byId(tree, 'notify-reply-state')[0])).toBe('Updated · ready again · v2'); + expect(text(byId(tree, 'notify-reply-state')[0])).toBe('Updated · ready again'); expect(text(byId(tree, 'notify-revision-asked')[0])).toBe('You asked: “Can it be undone?”'); expect(byId(tree, 'notify-revision-line').map(text)).toEqual(['• Added an undo step']); expect(tree.some(element => element.props.children === 'Reworded, was “Yes”')).toBe(true); @@ -98,9 +98,14 @@ describe('NotifySheet replies (P7)', () => { expect(byId(tree, 'notify-question-0')).toHaveLength(1); expect(byId(tree, 'notify-question-1')).toHaveLength(1); expect(byId(tree, 'notify-choice-0-1')).toHaveLength(1); - expect(text(byId(tree, 'notify-reply-state')[0])).toBe('Taken as a question: asking doozy-overhaul'); - expect(text(byId(tree, 'notify-reply-detail')[0])).toBe('Getting more context…'); - // Nothing picked or typed yet: Send waits. + expect(text(byId(tree, 'notify-reply-state')[0])).toBe('Doozy is getting more context'); + expect(text(byId(tree, 'notify-reply-detail')[0])).toBe('You wrote: “Why one?”'); + // Each question's why sits under it; a question without one shows none. + expect(text(byId(tree, 'notify-question-why-0')[0])).toBe('Trains cost money while on.'); + expect(byId(tree, 'notify-question-why-1')).toHaveLength(0); + // The first question starts on its recommended choice; the second has none, so Send waits. + expect(byId(tree, 'notify-choice-0-1')[0]?.props.accessibilityState).toEqual({ selected: true }); + expect(byId(tree, 'notify-choice-0-0')[0]?.props.accessibilityState).toEqual({ selected: false }); expect(byId(tree, 'notify-send-reply')[0]?.props.disabled).toBe(true); }); }); diff --git a/native/src/features/notify/NotifySheet.tsx b/native/src/features/notify/NotifySheet.tsx index 24378236c..c5822dd13 100644 --- a/native/src/features/notify/NotifySheet.tsx +++ b/native/src/features/notify/NotifySheet.tsx @@ -14,7 +14,7 @@ import { monoFontFamily, useTheme } from '@/theme'; import { Icon, Text, TextField } from '@/ui'; import { useMachineNotifyCommand, useMachineNotifyShared, useNotifyCommand, useNotifyShared } from './hooks'; -import { type AnsweredHere, isReply, NOTIFY_KIND_LABEL, notifySheetState, optionsAnswer, replyStatus, revisionNote, type ReplyTone } from './notifyQuestions'; +import { type AnsweredHere, initialPicks, isReply, NOTIFY_KIND_LABEL, notifySheetState, optionsAnswer, replyStatus, revisionNote, type ReplyTone } from './notifyQuestions'; /** * One Notify question, drawn like the desktop card (frontend/src/components/notify/NotifyCard.tsx) in the app's sheet chrome. @@ -139,8 +139,8 @@ export function QuestionForm({ item, answer, busy, children }: { const [note, setNote] = useState(''); const [text, setText] = useState(''); const [showDetail, setShowDetail] = useState(false); - // Several questions: one pick each, sent together with Send. - const [picks, setPicks] = useState<string[]>([]); + // Several questions: one pick each, sent together with Send. Each starts on its recommended choice (J1). + const [picks, setPicks] = useState<string[]>(() => initialPicks(item)); const trimmedNote = note.trim(); // A permission prompt stays options only: its note rides on the choice, never as a reply. // A permission prompt and a choice-only chat question take only their options (F-10). @@ -182,6 +182,7 @@ export function QuestionForm({ item, answer, busy, children }: { <View key={`${questionIndex}:${question.title}`} testID={`notify-question-${questionIndex}`} style={styles.choices}> <Text variant="footnote" tone="muted">{`Question ${questionIndex + 1} of ${item.questions?.length ?? 0}`}</Text> <Text variant="callout" style={styles.bold} selectable>{question.title}</Text> + {question.why?.trim() ? <Text testID={`notify-question-why-${questionIndex}`} variant="footnote" tone="secondary" selectable>{question.why.trim()}</Text> : null} {question.choices.map((choice, index) => ( <ChoiceRow key={choice.label} diff --git a/native/src/features/notify/notifyQuestions.test.ts b/native/src/features/notify/notifyQuestions.test.ts index a1abe97ad..4a8e7cd52 100644 --- a/native/src/features/notify/notifyQuestions.test.ts +++ b/native/src/features/notify/notifyQuestions.test.ts @@ -2,7 +2,9 @@ import { describe, expect, it } from 'vitest'; import type { NotifyItem, NotifySharedState } from '@shared/types/notify'; -import { isReply, notifySheetState, optionsAnswer, replyStatus, revisionNote, waitingQuestions } from './notifyQuestions'; +import { approvalStatus } from '@shared/approvalStatus'; + +import { initialPicks, isReply, notifySheetState, optionsAnswer, replyStateLine, replyStatus, revisionNote, waitingQuestions } from './notifyQuestions'; const question: NotifyItem = { id: 'q1', ui: 'approve', title: 'Run the migration?', source: { paneId: 's1', name: 'api-fix', agent: 'Claude' }, state: 'waiting', createdAt: 2_000, @@ -88,27 +90,48 @@ describe('the reply states (P7, J19)', () => { expect(replyStatus(options)).toBeNull(); }); - it('reads a reply as being read, by Autopilot when it is the reader', () => { - expect(replyStatus({ ...options, reply: reply({ picks: ['Yes, turn it on'] }) })).toEqual({ text: 'Reading your reply…', tone: 'muted', detail: 'You wrote: “Only if it can be undone” (picked: Yes, turn it on)' }); - expect(replyStatus({ ...options, reply: reply({ picks: [''], reader: { kind: 'autopilot', name: 'Autopilot' } }) })?.text).toBe('Autopilot is reading your reply…'); + // The words are the shared ones every surface uses (shared/approvalStatus.ts); the phone adds the quote and the fix. + it('names the reader for every kind while it reads the reply, and quotes what was sent', () => { + expect(replyStatus({ ...options, reply: reply({ picks: ['Yes, turn it on'], reader: { kind: 'asker', name: 'Okta' } }) })).toEqual({ text: 'Okta is reading your reply', tone: 'muted', detail: 'You wrote: “Only if it can be undone” (picked: Yes, turn it on)' }); + expect(replyStatus({ ...options, reply: reply({ picks: [''], reader: { kind: 'autopilot', name: 'Autopilot' } }) })?.text).toBe('Autopilot is reading your reply'); + expect(replyStatus({ ...options, reply: reply({ reader: { kind: 'orchestrator', name: 'The Doozy orchestrator' } }) })?.text).toBe('The Doozy orchestrator is reading your reply'); + // No reader named: the asking Pane. + expect(replyStateLine({ ...options, source: { ...options.source, name: 'Api-fix' }, reply: reply({ reader: undefined }) })?.text).toBe('Api-fix is reading your reply'); }); it('says how a reply was taken, with the one-tap fix until the person makes one', () => { - expect(replyStatus({ ...options, state: 'answered', reply: reply({ state: 'answer' }) })).toMatchObject({ text: 'Taken as your answer', tone: 'success', fix: { as: 'question', label: 'It was a question' } }); - expect(replyStatus({ ...options, reply: reply({ state: 'question' }) })).toEqual({ text: 'Taken as a question: asking doozy-overhaul', tone: 'warning', detail: 'Getting more context…', fix: { as: 'answer', label: 'It was my answer' } }); - expect(replyStatus({ ...options, reply: reply({ state: 'question', status: 'Checking the rollback path', fixed: 'question' }) })).toEqual({ text: 'Taken as a question: asking doozy-overhaul', tone: 'warning', detail: 'Checking the rollback path' }); + const okta = { kind: 'asker' as const, name: 'Okta' }; + expect(replyStatus({ ...options, state: 'answered', answerSummary: 'Yes, turn it on', reply: reply({ state: 'answer', reader: okta }) })).toMatchObject({ text: 'Taken as your answer: “Only if it can be undone”', tone: 'success', fix: { as: 'question', label: 'It was a question' } }); + expect(replyStatus({ ...options, reply: reply({ state: 'question', reader: okta }) })).toEqual({ text: 'Okta is getting more context', tone: 'warning', detail: 'You wrote: “Only if it can be undone”', fix: { as: 'answer', label: 'It was my answer' } }); + expect(replyStatus({ ...options, reply: reply({ state: 'question', status: 'Getting more context…', reader: okta }) })?.text).toBe('Okta is getting more context'); + expect(replyStatus({ ...options, reply: reply({ state: 'question', status: 'Checking the rollback path', fixed: 'question', reader: okta }) })).toEqual({ text: 'Okta is getting more context: Checking the rollback path', tone: 'warning', detail: 'You wrote: “Only if it can be undone”' }); + }); + + it('says where a built-in question went, and when no agent can read replies', () => { + expect(replyStatus({ ...options, state: 'answered', reply: reply({ state: 'sent', reader: { kind: 'asker', name: 'Claude' } }) })?.text).toBe('Sent to Claude'); + expect(replyStatus({ ...options, reply: reply({ state: 'unread', reader: undefined }) })?.text).toBe('No agent can read your reply right now. You can still pick an option.'); + }); + + it('reads "Updated · ready again" in blue once the approval came back updated', () => { + const updated: NotifyItem = { ...options, version: 2, readyAgain: 9_000 }; + expect(replyStatus(updated)).toEqual({ text: 'Updated · ready again', tone: 'accent' }); + expect(replyStatus({ ...updated, state: 'answered', readyAgain: undefined })).toBeNull(); }); - it('names the agent a built-in question went to, and says when no agent can read replies', () => { - expect(replyStatus({ ...options, state: 'answered', reply: reply({ state: 'sent' }) })?.text).toBe('Sent to Claude'); - expect(replyStatus({ ...options, reply: reply({ state: 'unread', reader: undefined }) })?.text).toBe('No agent can read replies right now. You can still pick an option.'); + it('uses the same words as every other surface', () => { + const items: NotifyItem[] = [ + { ...options, reply: reply({}) }, + { ...options, reply: reply({ state: 'question', status: 'Reading the logs' }) }, + { ...options, readyAgain: 9_000 }, + { ...options, state: 'answered', reply: reply({ state: 'answer', picks: ['Yes, turn it on'] }) }, + ]; + for (const item of items) expect(replyStateLine(item)?.text).toBe(approvalStatus(item)?.text); }); - it('reads "Updated · ready again" with its version until the person replies again', () => { - const updated: NotifyItem = { ...options, version: 2, readyAgain: 9_000, reply: reply({ state: 'question' }) }; - expect(replyStatus(updated)).toEqual({ text: 'Updated · ready again · v2', tone: 'accent' }); - expect(replyStatus({ ...updated, reply: reply({ at: 10_000 }) })?.text).toBe('Reading your reply…'); - expect(replyStatus({ ...updated, state: 'answered', reply: undefined })).toBeNull(); + it('starts each of several questions on its recommended choice', () => { + const two: NotifyItem = { ...options, choices: undefined, questions: [{ title: 'Turn it on?', choices: [{ label: 'Yes' }, { label: 'No', recommended: true }] }, { title: 'How many?', choices: [{ label: 'One' }] }] }; + expect(initialPicks(two)).toEqual(['No', '']); + expect(initialPicks(options)).toEqual([]); }); it('gives the detail screen What changed: the question asked and the asker’s lines', () => { diff --git a/native/src/features/notify/notifyQuestions.ts b/native/src/features/notify/notifyQuestions.ts index a2a947ceb..e81269e21 100644 --- a/native/src/features/notify/notifyQuestions.ts +++ b/native/src/features/notify/notifyQuestions.ts @@ -1,4 +1,5 @@ import { receiptLine } from '@shared/approvalReceipts'; +import { approvalStatus } from '@shared/approvalStatus'; import type { NotifyAnswer, NotifyItem, NotifyRevision, NotifySharedState, NotifyUi } from '@shared/types/notify'; /** The desktop card's kind labels (frontend/src/components/notify/notifyFormat.ts). */ @@ -81,29 +82,27 @@ export interface ReplyStatus { fix?: { as: 'answer' | 'question'; label: string }; } -/** The default status while a reader asks about a reply (`--took question` with no `--status`). */ -const GETTING_MORE_CONTEXT = 'Getting more context…'; +/** + * Where the person's reply stands, in the words every surface uses (`approvalStatus` in + * shared/approvalStatus.ts, which names the reader for every kind), with this phone's tone. + */ +export function replyStateLine(item: NotifyItem): { text: string; tone: ReplyTone } | null { + const status = approvalStatus(item); + return status ? { text: status.text, tone: status.tone === 'info' ? 'accent' : status.tone } : null; +} export function replyStatus(item: NotifyItem): ReplyStatus | null { + const line = replyStateLine(item); const reply = item.reply; - // Updated after the person's question, and not replied to since: the quiet "ready again". - if (item.state === 'waiting' && item.readyAgain !== undefined && !(reply && reply.at > item.readyAgain)) { - return { text: `Updated · ready again · v${item.version ?? 2}`, tone: 'accent' }; - } - if (!reply) return null; + if (!line || !reply || (item.state === 'waiting' && item.readyAgain !== undefined)) return line; const quote = `You wrote: “${reply.text}”${reply.picks?.some(Boolean) ? ` (picked: ${reply.picks.filter(Boolean).join(' · ')})` : ''}`; - const reader = reply.reader?.name ?? item.source.agent; switch (reply.state) { - case 'reading': - return { text: reply.reader?.kind === 'autopilot' ? 'Autopilot is reading your reply…' : 'Reading your reply…', tone: 'muted', detail: quote }; case 'answer': - return { text: 'Taken as your answer', tone: 'success', detail: quote, ...(reply.fixed ? {} : { fix: { as: 'question' as const, label: 'It was a question' } }) }; + return { ...line, detail: quote, ...(reply.fixed ? {} : { fix: { as: 'question' as const, label: 'It was a question' } }) }; case 'question': - return { text: `Taken as a question: asking ${reader}`, tone: 'warning', detail: reply.status?.trim() || GETTING_MORE_CONTEXT, ...(reply.fixed ? {} : { fix: { as: 'answer' as const, label: 'It was my answer' } }) }; - case 'sent': - return { text: `Sent to ${item.source.agent}`, tone: 'success', detail: quote }; - case 'unread': - return { text: 'No agent can read replies right now. You can still pick an option.', tone: 'warning', detail: quote }; + return { ...line, detail: quote, ...(reply.fixed ? {} : { fix: { as: 'answer' as const, label: 'It was my answer' } }) }; + default: + return { ...line, detail: quote }; } } @@ -136,6 +135,11 @@ export function optionsAnswer(item: NotifyItem, picks: readonly (string | undefi return item.questions ? { picks: all } : { choice: all[0] }; } +/** Several questions' first picks: each question's recommended choice, or none (J1). */ +export function initialPicks(item: NotifyItem): string[] { + return (item.questions ?? []).map(question => question.choices.find(choice => choice.recommended)?.label ?? ''); +} + /** A typed reply, which its reader takes as an answer or a question, rather than an answer as it stands. */ export function isReply(answer: NotifyAnswer): boolean { return 'picks' in answer && Boolean(answer.text?.trim()); diff --git a/native/src/features/panes/PaneListScreen.tsx b/native/src/features/panes/PaneListScreen.tsx index 504b33805..f1fe61b7d 100644 --- a/native/src/features/panes/PaneListScreen.tsx +++ b/native/src/features/panes/PaneListScreen.tsx @@ -1,6 +1,6 @@ import { LegendList } from '@legendapp/list/react-native'; import { router, useIsFocused } from 'expo-router'; -import { useState } from 'react'; +import { useState, type ReactNode } from 'react'; import { Alert, Pressable, StyleSheet, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -11,7 +11,8 @@ import type { PanePermissionRequest } from '@shared/types/permissions'; import { useDaemon } from '@/daemon'; import { AllMachinesHome, type AllMachinesFeed } from '@/features/allMachines/AllMachinesHome'; import { ApprovalGroups } from '@/features/approvals/ApprovalGroups'; -import { answeredItems, approvalItems, askerLine, isStale, itemEntry, questionDetail, sessionKey, sessionsByPane, staleItems, waitingCounts, type ApprovalEntry } from '@/features/approvals/approvals'; +import { answeredItems, approvalItems, askerLine, isStale, isWithAgent, itemEntry, questionDetail, sessionKey, sessionsByPane, staleItems, waitingCounts, type ApprovalEntry } from '@/features/approvals/approvals'; +import { approvalRowSpoken, ApprovalRowLines } from '@/features/approvals/ApprovalRowLines'; import { openApproval } from '@/features/approvals/ApprovalScreen'; import { AutopilotRow } from '@/features/autopilot/AutopilotRow'; import { useAutopilot } from '@/features/autopilot/hooks'; @@ -94,7 +95,7 @@ export function PaneListScreen() { const others = useOtherMachines(saved.flatMap(machine => (machine.profile && !machine.current ? [machine.profile] : [])), all && focused); const waiting = Object.values(pendingByPane(permissions.data ?? [])); - const questions = waitingQuestions(notify.data).filter(item => !isStale(item)); + const questions = waitingQuestions(notify.data).filter(item => !isStale(item) && !isWithAgent(item)); const blocked = new Set([...waiting.map(request => request.sessionId), ...questions.flatMap(item => item.source.paneId ?? [])]); const items = buildSidebar({ projects: projects.data ?? [], @@ -150,8 +151,10 @@ export function PaneListScreen() { testID={`notify-row-${entry.item.id}`} name={originLine(entry.item, profile.label)} detail={questionDetail(entry.item)} + below={<ApprovalRowLines item={entry.item} testID={`notify-row-${entry.item.id}`} />} + spoken={approvalRowSpoken(entry.item)} divided={divided} - done={list === 'answered'} + done={list === 'answered' || list === 'agent'} onPress={() => openApproval(entry.item.id, list)} /> {entry.item.state === 'waiting' ? <AgentPromptActions item={entry.item} inset={33} /> : null} @@ -440,13 +443,24 @@ function NewButton({ bottom }: { bottom: number }) { } /** A permission request or Notify question waiting on this phone's owner. */ -function WaitingRow({ name, detail, divided, done = false, onPress, testID }: { name: string; detail: string; divided: boolean; done?: boolean; onPress: () => void; testID: string }) { +function WaitingRow({ name, detail, below, spoken = [], divided, done = false, onPress, testID }: { + name: string; + detail: string; + /** A question's subtitle and reply status, under its title. */ + below?: ReactNode; + /** What `below` says, for screen readers. */ + spoken?: string[]; + divided: boolean; + done?: boolean; + onPress: () => void; + testID: string; +}) { const theme = useTheme(); return ( <Pressable testID={testID} accessibilityRole="button" - accessibilityLabel={`${name}, ${detail}`} + accessibilityLabel={[name, detail, ...spoken].join(', ')} onPress={onPress} style={({ pressed }) => [styles.waitingRow, pressed && { backgroundColor: theme.colors.surfacePressed }]} > @@ -455,6 +469,7 @@ function WaitingRow({ name, detail, divided, done = false, onPress, testID }: { <View style={styles.fill}> <Text variant="callout" numberOfLines={1} style={styles.bold}>{name}</Text> <Text variant="footnote" tone="muted" numberOfLines={2}>{detail}</Text> + {below} </View> <Icon ios="chevron.right" android="chevron_right" size={14} /> </Pressable> From 486bd0891ab31c67b9dbe2aa6ddda5beaaa6470a Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 17:50:13 -0800 Subject: [PATCH 08/13] Declare notify:summary-page daemon-owned, satisfy lint, document the new card and chat UI, add a Playwright check for the compact card and status line --- docs/PANE_NOTIFY.md | 41 ++++++--- .../src/components/approvals/approvalCard.ts | 2 +- .../src/components/approvals/summaryPage.ts | 2 +- main/src/ipc/notify.test.ts | 2 +- main/src/ipc/runpane.ts | 4 +- main/src/services/approvalsRules.ts | 6 +- main/src/services/notify/cardCheck.ts | 8 +- main/src/services/notify/notifyService.ts | 9 +- main/src/services/notify/summaryPage.ts | 6 +- shared/types/daemon.ts | 1 + tests/approvals-system.spec.ts | 92 +++++++++++++++++++ 11 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 tests/approvals-system.spec.ts diff --git a/docs/PANE_NOTIFY.md b/docs/PANE_NOTIFY.md index 0c286f9e8..b253c8f61 100644 --- a/docs/PANE_NOTIFY.md +++ b/docs/PANE_NOTIFY.md @@ -131,7 +131,7 @@ the first answer from the terminal, Approvals, the strip or the phone wins. **No**, and its alert carries the same two buttons. The question's sheet has every choice. - **Answered in the terminal**: Pane closes the question and lists it in - Answered as "Answered in the terminal". + Done today as "Answered in the terminal". - **The whole command, plan or tool input** is on the card, so Yes never approves something you couldn't read. One over 32,000 characters is too long to show: its question offers no choices and says to answer in the terminal. @@ -206,14 +206,14 @@ Codex panel also closes when that panel sits idle at its prompt for 10 seconds. shows an amber count of the approvals waiting from it. The row's count is the sum of every Session's count plus items not in a Session, across saved machines; in the one-machine sidebar its tooltip says how many of them wait on other machines. Beside - it, the queue has **Waiting** and **Answered** tabs and a **Group by** menu, + it, the queue has **Waiting** and **Done today** tabs and a **Group by** menu, remembered per device: **Most recent** (the default) lists items flat, newest first, each row naming its Session and machine; **By Session** and **By project** group them, each group placed by its newest item; **Sidebar order** groups by Session in the order the sidebar shows them (the All machines arrangement when that's on), following a drag there, with Sessions the sidebar doesn't show last, newest first. Each group is boxed under a header - with its name, machine mark and waiting count. Waiting sorts by when it was asked and Answered by when it was + with its name, machine mark and waiting count. Waiting sorts by when it was asked and Done today by when it was answered. A new item never moves the selection or the selected row. The list stays compact rows, each with a chip for "Reading your reply…", "Getting more context…" or "Updated · ready again". The selected item opens @@ -224,14 +224,21 @@ Codex panel also closes when that panel sits idle at its prompt for 10 seconds. standalone item shows the asking Pane's tabs, with **Open in Pane** (or **Open on <machine>** for another machine's item). Below about 1,100px wide, the list stacks above the column. After a pick, the column shows "Sent: - …" with **Undo** for 3 seconds; the item then moves to Answered, which lists - the last 24 hours, updates (`ui info`) included. Inside Pane there is no + …" with **Undo** for 3 seconds; the item then moves to **Done today**, + which lists the last 24 hours, updates (`ui info`) included. Under Most + recent, once a typed reply is with an agent, Waiting splits into **Waiting + on you** and **With the agent**, each row naming who reads the reply. Inside Pane there is no floating strip and no Notify row in the sidebar. - **The approval column**: one tabbed column for an approval, the same on every surface. Its header pages through what's waiting (‹ n of N ›, dots, ← - and → while it has focus) and ✕ closes it. Its tabs are **Questions & - answers** (the options and one reply box), **Summary** (written by Pane from - the approval), each linked file (in the document viewer), URL (in the + and → while it has focus) and ✕ closes it. Its tabs are **Decide** (the + subtitle, each question with its why and options, recommended ones + pre-selected, the references and one reply box that grows up to half the + column), **Summary** (one HTML page: the asker's own, an `.html` passed as a + worker's `--summary-file` or `summary` in `needs_you`, else one Pane writes + from the card and the worker's status file; it renders with scripts and + network off, the same on the Mac, from another machine and on the phone), + each linked file (in the document viewer), URL (in the in-app browser) or other approval (in place, with Back), and the chat where it was asked, left out when you're already in it. In a **Session**, the column lays over the Session's right split view without resizing it, and ✕ @@ -240,9 +247,15 @@ Codex panel also closes when that panel sits idle at its prompt for 10 seconds. sidebar (Overview, Files, Changes) is never covered. The Session's title bar shows **Approvals n · n ready**, and a Session row's count in the sidebar opens the newest ready-again approval, or the first waiting one. A - Session's orchestrator chat docks light cards above its composer: title, - one line, a one-question approval's options as buttons, and **Open on the - right**. In **Autopilot**, the column covers the Stack (✕ "Back to the + Session's orchestrator chat docks one compact card above its composer: + who asks, the title and subtitle across the card, and a bottom row with ‹ n + of m ›, the reference count, Summary and **Open**; its pager stays in step + with the column's. The chat's own AskUserQuestion isn't docked: the chat + already shows Claude's card, and Approvals and the phone keep the copy. + **Send** closes the column, and a status line in the chat names the agent + reading the reply ("okta-sso-account is reading your reply to Q-42"), then + how it was taken, with **Reopen**. "Taken as your answer" shows only on that + approval, its row and that line. In **Autopilot**, the column covers the Stack (✕ "Back to the Stack"), and Autopilot's decision cards gain **Open**. In a Session and in Autopilot, the chat tab is live too: an orchestrator's item shows its Session, and a worker's or a Pane's item shows the tab that asked. @@ -386,9 +399,9 @@ so older items simply have none. | State | Shows | | --- | --- | | Waiting, no reply | Waiting | -| Reply delivered, not yet taken | "Reading your reply…" ("Autopilot is reading your reply…") | +| Reply delivered, not yet taken | "<reader> is reading your reply…", naming the reader for every kind ("okta-sso-account", "The Doozy orchestrator", "Autopilot") | | Taken as an answer | "Taken as your answer", with **It was a question** | -| Taken as a question | "Taken as a question: asking <reader>" and its status ("Getting more context…"), with **It was my answer** | +| Taken as a question | "Taken as a question: <reader> is getting more context", with its status when the reader gave one, and **It was my answer** | | Updated | "Updated · ready again": your question quoted ("You asked"), What changed and by whom, choices marked NEW, REWORDED (the old label struck) or REMOVED, and a collapsed **Earlier version** | | No reader | "No agent can read replies right now. You can still pick an option." | | A chat tab's built-in question | "Sent to <agent>": the typed reply is its answer, so there is no fix | @@ -414,7 +427,7 @@ An approval exists only while you can act on it and someone can take the answer | Pane restarts | Items whose asking tab still exists stay waiting. | | Unanswered for 3 days | Stale: moved to a collapsed **Stale** group at the bottom of Waiting. It never rings or pushes again. | | You close one | One action on any card, with an optional reason. Its asker's open call returns "The person closed this question.", or `closed: Q-n (by you)` is typed into its tab. **Reopen** puts it back. | -| You or Autopilot answered | Answered lists it for 24 hours. | +| You or Autopilot answered | Done today lists it for 24 hours. | The store keeps answered and closed items 7 days for the record and for orchestrators catching up on decisions (`runpane inbox`), then drops them. On the diff --git a/frontend/src/components/approvals/approvalCard.ts b/frontend/src/components/approvals/approvalCard.ts index af6c63ecd..c47eacaa0 100644 --- a/frontend/src/components/approvals/approvalCard.ts +++ b/frontend/src/components/approvals/approvalCard.ts @@ -119,7 +119,7 @@ export function replyBeingRead(item: NotifyItem): boolean { } /** Being read or asked about: Send moves past it (J14). */ -export function isBeingRead(item: NotifyItem): boolean { +function isBeingRead(item: NotifyItem): boolean { return item.state === 'waiting' && (item.reply?.state === 'reading' || item.reply?.state === 'question'); } diff --git a/frontend/src/components/approvals/summaryPage.ts b/frontend/src/components/approvals/summaryPage.ts index 61db59f28..657118398 100644 --- a/frontend/src/components/approvals/summaryPage.ts +++ b/frontend/src/components/approvals/summaryPage.ts @@ -17,7 +17,7 @@ const templateSchema = boundary.object({ kind: boundary.literal('template'), htm const missingSchema = boundary.object({ kind: boundary.literal('missing'), message: boundary.string }); /** The id the holding machine knows the approval by: a saved machine's card is `<machineId>:<its id>`. */ -export function hostItemId(item: Pick<NotifyItem, 'id' | 'machineId'>): string { +function hostItemId(item: Pick<NotifyItem, 'id' | 'machineId'>): string { const machine = item.machineId; return machine && machine !== 'local' && item.id.startsWith(`${machine}:`) ? item.id.slice(machine.length + 1) : item.id; } diff --git a/main/src/ipc/notify.test.ts b/main/src/ipc/notify.test.ts index 66c1c52db..912beca66 100644 --- a/main/src/ipc/notify.test.ts +++ b/main/src/ipc/notify.test.ts @@ -631,7 +631,7 @@ describe('Approvals items, through the registered handlers', () => { const pane = await startWithSessions(); const caller = { paneId: 'loose', panelId: 'loose-panel' }; const choices = [{ label: 'Ship', detail: 'Goes out tonight.', recommended: true }, { label: 'Wait', detail: 'Ships Monday.' }]; - const refused = async (request: Record<string, PaneCommandValue>) => pane.registry.invoke('runpane:notify', [{ ui: 'options', caller, choices, context: 'CI is green.', ...request }]).then(() => undefined, (error: unknown) => error); + const refused = async (request: Record<string, PaneCommandValue>) => pane.registry.invoke('runpane:notify', [{ ui: 'options', caller, choices, context: 'CI is green.', ...request }]).then(() => undefined, (error: PaneError) => error); expect(await refused({ title: 'x'.repeat(200) })).toMatchObject({ code: 'ERR_NOTIFY_TITLE', next: expect.stringContaining('--context') }); expect(await refused({ title: 'Ship it?\nCI is green.' })).toMatchObject({ code: 'ERR_NOTIFY_TITLE' }); expect(await refused({ title: 'Ship it?', choices: choices.map(choice => ({ ...choice, recommended: true })) })).toMatchObject({ code: 'ERR_NOTIFY_RECOMMENDED' }); diff --git a/main/src/ipc/runpane.ts b/main/src/ipc/runpane.ts index b6ef1d4f7..3731d4b0d 100644 --- a/main/src/ipc/runpane.ts +++ b/main/src/ipc/runpane.ts @@ -1920,7 +1920,9 @@ export function registerRunpaneHandlers( report: journalAgentReport(report), }); const sessionIds = await services.orchestrationSessionManager?.recordAgentReport(panel.id, report) ?? []; - return { ok: true, paneId: panel.sessionId, panelId: panel.id, report, sessionIds, liveWatchers, ...(warnings.length ? { warnings } : {}) }; + const result: RunpaneReportResult = { ok: true, paneId: panel.sessionId, panelId: panel.id, report, sessionIds, liveWatchers }; + if (warnings.length) result.warnings = warnings; + return result; }, result => ({ paneId: result.paneId, panelId: result.panelId, ok: result.ok })); }); diff --git a/main/src/services/approvalsRules.ts b/main/src/services/approvalsRules.ts index 1b3999704..71accca49 100644 --- a/main/src/services/approvalsRules.ts +++ b/main/src/services/approvalsRules.ts @@ -6,7 +6,7 @@ */ /** Which card to use: Claude's own card for a quick question in the chat the person is in, an approval for everything else. */ -export const WHICH_CARD_RULE = [ +const WHICH_CARD_RULE = [ '### Which card', '', "- **Claude's own card (AskUserQuestion)** only when all of these hold: you are answering the person's latest message in the chat they are typing in; the answer only steers this turn (nothing recorded as a decision, nothing for another agent, nothing irreversible or outward-facing); and the question stands on its own, without context, links or a Summary. Examples: \"Update the snapshot too, or leave it?\", \"Which of these two files did you mean?\"", @@ -16,7 +16,7 @@ export const WHICH_CARD_RULE = [ ].join('\n'); /** What a good approval card holds. Pane refuses the two clear mistakes and warns on the rest. */ -export const CARD_CONTRACT = [ +const CARD_CONTRACT = [ '### The card', '', '- **Title**: what you need, about 80 to 100 characters on one line. Pane refuses a title over 160 characters or with a line break.', @@ -27,7 +27,7 @@ export const CARD_CONTRACT = [ ].join('\n'); /** After the person replies: say how you took it, and update the card when it was a question. */ -export const REPLY_RULE = [ +const REPLY_RULE = [ '### Replies', '', '- A typed reply may be an answer or a question. Whoever reads it runs `runpane notify --took <id|Q-n> answer` or `--took <id|Q-n> question`.', diff --git a/main/src/services/notify/cardCheck.ts b/main/src/services/notify/cardCheck.ts index 7f0063b37..0999a8479 100644 --- a/main/src/services/notify/cardCheck.ts +++ b/main/src/services/notify/cardCheck.ts @@ -13,11 +13,11 @@ import type { NotifyChoice, NotifyLink, NotifySubQuestion } from '../../../../sh /** A title past this is refused. */ export const CARD_TITLE_REFUSE = 160; /** A title past this gets a warning. */ -export const CARD_TITLE_WARN = 100; +const CARD_TITLE_WARN = 100; /** A subtitle past this gets a warning. */ -export const CARD_SUBTITLE_WARN = 240; +const CARD_SUBTITLE_WARN = 240; /** More options than this get a warning. */ -export const CARD_OPTIONS_WARN = 4; +const CARD_OPTIONS_WARN = 4; export interface CardFields { title: string; @@ -27,7 +27,7 @@ export interface CardFields { links?: NotifyLink[]; } -export interface CardRefusal { +interface CardRefusal { code: 'ERR_NOTIFY_TITLE' | 'ERR_NOTIFY_RECOMMENDED'; message: string; next: string; diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index cb3e1491e..593b0a8bd 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -767,9 +767,14 @@ function appearanceKey(config: AppConfig): string { * A worker card's Summary page and status file (P4): an .html passed as --summary-file is its * Summary; any other file is its status file, whose needs_you may name a Summary page beside it. */ -export function summaryFiles(summaryPath: string | undefined, needsYouPage: string | undefined): { summaryPage?: string; statusFile?: string } { +export interface WorkerSummaryFiles { + summaryPage?: string; + statusFile?: string; +} + +export function summaryFiles(summaryPath: string | undefined, needsYouPage: string | undefined): WorkerSummaryFiles { if (summaryPath && /\.html?$/iu.test(summaryPath)) return { summaryPage: summaryPath }; - const files: { summaryPage?: string; statusFile?: string } = {}; + const files: WorkerSummaryFiles = {}; if (summaryPath) files.statusFile = summaryPath; if (needsYouPage) files.summaryPage = path.isAbsolute(needsYouPage) || !summaryPath ? needsYouPage : path.resolve(path.dirname(summaryPath), needsYouPage); return files; diff --git a/main/src/services/notify/summaryPage.ts b/main/src/services/notify/summaryPage.ts index 50f5ec7f9..f960ad42c 100644 --- a/main/src/services/notify/summaryPage.ts +++ b/main/src/services/notify/summaryPage.ts @@ -76,13 +76,13 @@ function stateLabel(item: NotifyItem): string { return 'waiting on you'; } -const PALETTE: Record<SummaryTheme, string> = { +const PALETTE = { light: '--bg:#ffffff;--text:#1f2328;--muted:#59636e;--line:#d1d9e0;--tint:#eef6ee;--accent:#1a7f37;--chip:#9a6700;--chipbg:#fff8c5;--code:#f6f8fa', dark: '--bg:#0d1117;--text:#e6edf3;--muted:#9198a1;--line:#3d444d;--tint:#12261e;--accent:#3fb950;--chip:#d29922;--chipbg:#2e2a1a;--code:#161b22', -}; +} satisfies Record<SummaryTheme, string>; /** The page Pane writes when the asker sent none: the card, each option and its consequence, where it stands, references. */ -export function summaryTemplate(item: NotifyItem, theme: SummaryTheme = 'light'): string { +function summaryTemplate(item: NotifyItem, theme: SummaryTheme = 'light'): string { const asked = new Date(item.createdAt); const time = `${String(asked.getHours()).padStart(2, '0')}:${String(asked.getMinutes()).padStart(2, '0')}`; const qn = /^(Q-\d+)\b/u.exec(item.title)?.[1]; diff --git a/shared/types/daemon.ts b/shared/types/daemon.ts index f6f39f701..583496def 100644 --- a/shared/types/daemon.ts +++ b/shared/types/daemon.ts @@ -151,6 +151,7 @@ export const DAEMON_OWNED_EXACT_CHANNELS = [ 'permission:respond', 'notify:remote-snapshot', 'notify:remote-command', + 'notify:summary-page', 'file:copy', 'file:delete', 'file:duplicate', diff --git a/tests/approvals-system.spec.ts b/tests/approvals-system.spec.ts new file mode 100644 index 000000000..77bda8d03 --- /dev/null +++ b/tests/approvals-system.spec.ts @@ -0,0 +1,92 @@ +import { expect, test, type Page } from '@playwright/test'; +import { installElectronApiMock } from './electronApiMock'; +import { DEFAULT_NOTIFY_SETTINGS, type NotifyItem, type NotifySnapshot } from '../shared/types/notify'; + +// The approvals system's chat surfaces (P5, P6, P7): one compact card with a pager above a +// Session chat's composer, no copy of the chat's own question, and a status line naming the +// agent reading a reply. Rendered from a Notify snapshot; no app, no agent. + +const now = new Date(0).toISOString(); +const project = { id: 81, name: 'Approvals fixture', path: '/tmp/approvals-fixture', active: true, created_at: now, updated_at: now }; +const session = { id: 's1', name: 'Orgs, Workspaces, Versioning', paneId: 'session-pane' }; +const pane = { + id: 'session-pane', name: 'orgs-orchestrator', worktreePath: '/tmp/approvals-fixture/session-pane', prompt: '', status: 'stopped', createdAt: now, lastActivity: now, + output: [], jsonMessages: [], isRunning: false, permissionMode: 'ignore', projectId: project.id, displayOrder: 0, + isFavorite: false, toolType: 'none', archived: false, + gitStatus: { state: 'clean', ahead: 0, behind: 0, hasUncommittedChanges: false, hasUntrackedFiles: false, filesChanged: 0 }, +}; +const panels = [ + { id: 'dock', sessionId: 'session-pane', type: 'terminal', title: 'Terminal', state: { isActive: false, hasBeenViewed: true, customState: { isInitialized: false } }, metadata: { createdAt: now, lastActiveAt: now, position: 0 } }, + { + id: 'chat', sessionId: 'session-pane', type: 'chat', title: 'Claude', + state: { isActive: true, hasBeenViewed: true, customState: { agent: 'claude', launch: { kind: 'new' }, cwd: pane.worktreePath, status: 'idle', orchestrationSessionId: session.id } }, + metadata: { createdAt: now, lastActiveAt: now, position: 1 }, + }, +]; + +function approval(id: string, title: string, minutesAgo: number, extra: Partial<NotifyItem> = {}): NotifyItem { + return { + id, ui: 'options', title, state: 'waiting', createdAt: Date.now() - minutesAgo * 60_000, originKind: 'worker', + source: { paneId: 'worker-pane', panelId: 'worker-panel', name: 'okta-sso-account', agent: 'Claude', session }, + choices: [{ label: 'Keep it', detail: 'They lose only this account’s orgs.', recommended: true }, { label: 'Sign them out everywhere' }], + ...extra, + }; +} + +const q42 = approval('q42', 'Okta sign-out rework: two choices before the cover sheet is approved', 2, { + context: 'The one-day check passed: option A holds (5 of 5 checks, 2 with caveats the plan absorbs). Cover sheet r1 is ready, and approving it needs these two answers.', + choices: undefined, + questions: [ + { title: 'With Require SSO off, does someone Okta removes keep their password session for their other orgs?', choices: [{ label: 'Keep it', recommended: true }, { label: 'Sign them out everywhere' }] }, + { title: 'When Require SSO is on, whose password sessions end?', why: 'Same rule as SSO sign-in.', choices: [{ label: 'Everyone on the domain', recommended: true }, { label: 'Only the account’s members' }] }, + ], + links: [{ label: 'Cover sheet r1', path: '/tmp/okta/cover-sheet.html' }, { label: 'Decision log § Q-42', path: '/tmp/okta/decisions.html#q-42' }, { label: 'okta-sso-account status', path: '/tmp/okta/status.json' }], +}); +const q44 = approval('q44', 'Q-44: Merge #1521 once the follow-up review passes?', 9, { context: 'The follow-up review checks only the fix commit. CI is green; merging unblocks P3.', source: { ...q42.source, name: 'p2-review' } }); +const q45 = approval('q45', 'Q-45: Show the version badge in the web footer too?', 21, { context: 'The badge ships in the app header today.' }); +/** The chat's own AskUserQuestion, copied to Approvals: the chat already shows Claude's card. */ +const ownQuestion = approval('own', 'Update the snapshot too, or leave it?', 1, { replyIsAnswer: true, source: { paneId: 'session-pane', panelId: 'chat', name: 'orgs-orchestrator', agent: 'Claude', session: { ...session, orchestrator: true } } }); + +function snapshot(items: NotifyItem[]): NotifySnapshot { + return { + settings: { ...DEFAULT_NOTIFY_SETTINGS }, items, queue: items.filter(item => item.state === 'waiting').map(item => item.id), hidden: [], strip: null, stripSurface: null, + pausedUntil: null, shortcutRegistered: true, showOutsideSupported: true, firstRun: false, instructionFiles: [], + }; +} + +async function openChat(page: Page, items: NotifyItem[]): Promise<void> { + await installElectronApiMock(page, { initialProjects: [project], initialSessions: [pane], initialPanels: panels, activeProjectId: project.id, notifySnapshot: snapshot(items) }); + await page.setViewportSize({ width: 1440, height: 900 }); + await page.goto('/'); + await page.getByRole('button', { name: 'Expand project Approvals fixture', exact: true }).click(); + await page.getByRole('button', { name: 'orgs-orchestrator', exact: true }).click(); + await expect(page.getByRole('textbox', { name: 'Message Claude' })).toBeVisible(); +} + +test('J6, P5: the chat shows one compact card with a pager and no options, leaving out its own question', async ({ page }, testInfo) => { + await openChat(page, [q42, q44, q45, ownQuestion]); + const cards = page.getByTestId('session-approval-card'); + await expect(cards).toHaveCount(1); + // Oldest first, as the column's pager walks them; the chat's own AskUserQuestion isn't one of them. + await expect(page.getByTestId('session-approval-card-pager')).toHaveText('1 of 3'); + await expect(page.getByTestId('session-approval-card-title')).toHaveText(q45.title); + await expect(page.getByTestId('session-approval-card-subtitle')).toHaveText(q45.context ?? ''); + await expect(page.getByTestId('session-approval-card-choice')).toHaveCount(0); + await page.getByTestId('session-approval-card-next').click(); + await page.getByTestId('session-approval-card-next').click(); + await expect(page.getByTestId('session-approval-card-pager')).toHaveText('3 of 3'); + await expect(page.getByTestId('session-approval-card-title')).toHaveText(q42.title); + await expect(cards).toContainText('2 questions'); + await expect(cards).toContainText('3 references'); + await page.screenshot({ path: testInfo.outputPath('08-compact-card.png') }); +}); + +test('J8: after a send, a status line names the agent reading the reply, and the next approval takes the card', async ({ page }, testInfo) => { + const sent = { ...q42, title: `Q-42: ${q42.title}`, reply: { id: 'r1', text: 'Keep it, but say so in the docs', picks: ['Keep it', 'Everyone on the domain'], at: Date.now(), state: 'reading' as const, reader: { kind: 'asker' as const, name: 'okta-sso-account' }, deliveredAt: Date.now() } }; + await openChat(page, [sent, q44]); + const line = page.getByTestId('session-approval-status'); + await expect(line).toHaveText(/okta-sso-account is reading your reply to Q-42…\s*Reopen/u); + await expect(page.getByTestId('session-approval-card-pager')).toHaveText('1 of 1'); + await expect(page.getByTestId('session-approval-card-title')).toHaveText(q44.title); + await page.screenshot({ path: testInfo.outputPath('10-reading.png') }); +}); From 22f0f3828bb9bde82b1baa48b3d8d9db90f0d1a8 Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 18:36:33 -0800 Subject: [PATCH 09/13] =?UTF-8?q?Fix=20QA=20findings=20on=20#1311:=20an=20?= =?UTF-8?q?.html=20--summary-file=20is=20never=20the=20subtitle,=20a=20rel?= =?UTF-8?q?ative=20needs=5Fyou=20summary=20is=20found=20from=20the=20worke?= =?UTF-8?q?r's=20folder,=20the=20chat=20line=20says=20Updated=20=C2=B7=20r?= =?UTF-8?q?eady=20again,=20list=20rows=20keep=20their=20titles,=20tidy=20b?= =?UTF-8?q?anner=20and=20help=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/runpane/contract.json | 14 ++++++------ .../components/approvals/ApprovalColumn.tsx | 14 +++++------- .../components/approvals/ApprovalsView.tsx | 3 ++- .../approvals/SessionApprovalCards.tsx | 6 ++--- .../components/approvals/approvalCard.test.ts | 3 +++ .../src/components/approvals/approvalCard.ts | 11 ++++++++-- main/src/ipc/runpane.ts | 2 +- main/src/services/notify/cardCheck.ts | 4 ++-- main/src/services/notify/notifyService.ts | 22 +++++++++++++++++-- main/src/services/notify/summaryPage.test.ts | 6 ++++- main/src/services/notify/summaryPage.ts | 4 ++-- .../src/services/notify/workerContext.test.ts | 7 ++++++ main/src/services/notify/workerContext.ts | 9 +++++--- .../src/runpane/generated_contract.py | 2 +- packages/runpane/src/generated/contract.ts | 14 ++++++------ shared/types/generatedRunpaneContract.ts | 14 ++++++------ 16 files changed, 88 insertions(+), 47 deletions(-) diff --git a/contracts/runpane/contract.json b/contracts/runpane/contract.json index 29231680c..c94ff3414 100644 --- a/contracts/runpane/contract.json +++ b/contracts/runpane/contract.json @@ -2546,7 +2546,7 @@ { "name": "--context", "value": "<text>", - "description": "notify: background shown under Details." + "description": "notify: the card's subtitle: one or two sentences on why it matters (up to 240 characters)." }, { "name": "--choices", @@ -4138,7 +4138,7 @@ "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -4707,7 +4707,7 @@ "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -6052,7 +6052,7 @@ "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -6575,7 +6575,7 @@ "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -23045,7 +23045,7 @@ "name": "--context", "value": "<text>", "required": false, - "description": "Why it matters, shown under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", @@ -23547,7 +23547,7 @@ "name": "--context", "value": "<text>", "required": false, - "description": "Background the person can open under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", diff --git a/frontend/src/components/approvals/ApprovalColumn.tsx b/frontend/src/components/approvals/ApprovalColumn.tsx index 0af4f89a0..cda58027c 100644 --- a/frontend/src/components/approvals/ApprovalColumn.tsx +++ b/frontend/src/components/approvals/ApprovalColumn.tsx @@ -168,9 +168,9 @@ function Chip({ chip, testId }: { chip: NonNullable<CardChip>; testId?: string } const tone = chip.tone === 'info' ? 'bg-status-info/10 text-status-info-on-tint' : chip.tone === 'warning' ? 'bg-status-warning/10 text-status-warning-on-tint' : chip.tone === 'success' ? 'bg-status-success/10 text-status-success-on-tint' : 'bg-surface-tertiary text-text-secondary'; const Icon = chip.tone === 'info' ? Sparkles : chip.tone === 'warning' ? Loader2 : chip.tone === 'success' ? Check : Loader2; return ( - <span className={cn('inline-flex h-[22px] shrink-0 items-center gap-1 rounded-full px-2 text-[12px] font-medium', tone)} data-testid={testId ?? 'approval-chip'} data-tone={chip.tone}> - <span className="flex h-3 w-3 items-center justify-center" aria-hidden="true"><Icon className="h-3 w-3" /></span> - {chip.text} + <span className={cn('inline-flex h-[22px] min-w-0 max-w-full shrink-0 items-center gap-1 rounded-full px-2 text-[12px] font-medium', tone)} data-testid={testId ?? 'approval-chip'} data-tone={chip.tone} title={chip.text}> + <span className="flex h-3 w-3 shrink-0 items-center justify-center" aria-hidden="true"><Icon className="h-3 w-3" /></span> + <span className="truncate">{chip.text}</span> </span> ); } @@ -315,7 +315,7 @@ function QuestionsTab({ item, onSent, tabs, onOpenTab }: { item: NotifyItem; onS <div className="mt-2 rounded-lg bg-status-warning/10 px-3 py-2 text-[13px] text-status-warning-on-tint" data-testid="approval-tidied"> <div className="flex items-start gap-2"> <Sparkles className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" /> - <span className="min-w-0 flex-1">Tidied by Pane: sent as one {[...tidied.title].length}-character question. Split by rule; every word is the sender’s.</span> + <span className="min-w-0 flex-1">Tidied by Pane: sent as one {[...tidied.title].length}-character question and split by rule. Show as sent has it exactly as written.</span> <button type="button" className="shrink-0 font-medium hover:underline" aria-expanded={showSent} onClick={() => setShowSent((shown) => !shown)} data-testid="approval-show-as-sent"> {showSent ? 'Hide' : 'Show as sent'} </button> @@ -359,7 +359,6 @@ function QuestionsTab({ item, onSent, tabs, onOpenTab }: { item: NotifyItem; onS key={`${at}:${question.title}`} question={question} label={questions.length > 1 ? `Question ${at + 1} of ${questions.length}` : 'Question'} - noDetail={tidied ? 'No description was sent.' : undefined} picked={picks[at]} disabled={!waiting} onPick={(label) => setPicks((current) => { const next = [...current]; next[at] = current[at] === label ? '' : label; return next; })} @@ -490,7 +489,7 @@ function ReplyNotice({ item }: { item: NotifyItem }) { ); } -function QuestionBlock({ question, label, picked, disabled, onPick, showTitle, noDetail }: { question: CardQuestion; label: string; picked?: string; disabled: boolean; onPick: (label: string) => void; showTitle: boolean; noDetail?: string }) { +function QuestionBlock({ question, label, picked, disabled, onPick, showTitle }: { question: CardQuestion; label: string; picked?: string; disabled: boolean; onPick: (label: string) => void; showTitle: boolean }) { return ( <section className="mt-4 rounded-xl border border-border-secondary p-4" data-testid="approval-question"> <div className="text-[12px] font-medium uppercase tracking-wide text-text-tertiary">{label}</div> @@ -527,8 +526,7 @@ function QuestionBlock({ question, label, picked, disabled, onPick, showTitle, n {choice.mark === 'new' && <span className="rounded bg-status-success/10 px-1.5 py-px text-[11px] font-semibold uppercase tracking-wide text-status-success-on-tint" data-testid="approval-mark">New</span>} {choice.mark === 'reworded' && <span className="rounded bg-status-info/10 px-1.5 py-px text-[11px] font-semibold uppercase tracking-wide text-status-info-on-tint" data-testid="approval-mark">Reworded</span>} </span> - {choice.detail ? <span className="mt-0.5 block text-[13px] leading-relaxed text-text-secondary">{choice.detail}</span> - : noDetail && <span className="mt-0.5 block text-[13px] leading-relaxed text-text-tertiary">{noDetail}</span>} + {choice.detail && <span className="mt-0.5 block text-[13px] leading-relaxed text-text-secondary">{choice.detail}</span>} {choice.before && ( <span className="mt-1 block text-[12.5px] leading-relaxed text-text-tertiary line-through" data-testid="approval-was"> was: {choice.before.label !== choice.label ? `${choice.before.label}${choice.before.detail ? `: ${choice.before.detail}` : ''}` : choice.before.detail ?? ''} diff --git a/frontend/src/components/approvals/ApprovalsView.tsx b/frontend/src/components/approvals/ApprovalsView.tsx index 973aa6107..b3b4710ce 100644 --- a/frontend/src/components/approvals/ApprovalsView.tsx +++ b/frontend/src/components/approvals/ApprovalsView.tsx @@ -107,7 +107,8 @@ function ItemRow({ item, now, selected = false, onSelect }: { item: NotifyItem; {item.unreachable ? ` · ${item.machine ?? 'its machine'} can't be reached` : ''} </span> </span> - <span className="flex shrink-0 flex-col items-end gap-1"> + {/* The chip may name a long reader: it gives way to the title, never the other way round. */} + <span className="flex min-w-0 max-w-[45%] shrink-0 flex-col items-end gap-1"> <span className="text-[12px] tabular-nums text-text-tertiary">{relativeAge(item.resolvedAt ?? item.createdAt, now)}</span> {chip && chip.tone !== 'success' && <ApprovalChip chip={chip} testId="approvals-row-chip" />} {taken?.kind === 'taken' && <ApprovalChip chip={{ tone: 'success', text: 'Taken as your answer' }} testId="approvals-row-chip" />} diff --git a/frontend/src/components/approvals/SessionApprovalCards.tsx b/frontend/src/components/approvals/SessionApprovalCards.tsx index de5e36746..c4643c655 100644 --- a/frontend/src/components/approvals/SessionApprovalCards.tsx +++ b/frontend/src/components/approvals/SessionApprovalCards.tsx @@ -7,7 +7,7 @@ import { runNotify, selectNotifyItems, useNotifyStore } from '../../stores/notif import { openSessionApproval, useApprovalColumnStore } from '../../stores/approvalColumnStore'; import { relativeAge } from '../notify/notifyFormat'; import { useNow } from '../notify/useNow'; -import { dockApprovals, latestReplied, questionsOf, referenceCount } from './approvalCard'; +import { dockApprovals, latestReplied, questionsOf, referenceCount, statusKey } from './approvalCard'; import { useSessionApprovals } from './useSessionApprovals'; /** @@ -114,7 +114,7 @@ function ReplyStatusLine({ sessionId }: { sessionId: string }) { if (!item || !status) return null; const Icon = status.kind === 'reading' ? Loader2 : status.kind === 'taken' || status.kind === 'sent' ? Check : status.kind === 'ready-again' ? Sparkles : CircleAlert; const fix = status.kind === 'taken' && !item.reply?.fixed ? { as: 'question' as const, label: 'It was a question' } : status.kind === 'asking' && !item.reply?.fixed ? { as: 'answer' as const, label: 'It was my answer' } : undefined; - const dismiss = () => setDismissed((current) => new Set([...current, item.reply?.id ?? item.id])); + const dismiss = () => setDismissed((current) => new Set([...current, statusKey(item)])); return ( <div className={cn('flex items-center gap-2 rounded-xl px-3.5 py-2 text-[13.5px]', LINE_TONE[status.tone])} role="status" data-testid="session-approval-status" data-kind={status.kind} data-item-id={item.id}> <span className="flex h-3.5 w-3.5 shrink-0 items-center justify-center" aria-hidden="true"> @@ -131,7 +131,7 @@ function ReplyStatusLine({ sessionId }: { sessionId: string }) { Reopen </button> )} - {(status.kind === 'taken' || status.kind === 'sent') && ( + {(status.kind === 'taken' || status.kind === 'sent' || status.kind === 'ready-again') && ( <button type="button" aria-label="Dismiss" onClick={dismiss} className="shrink-0 text-text-tertiary hover:text-text-primary"><X className="h-3.5 w-3.5" aria-hidden="true" /></button> )} </div> diff --git a/frontend/src/components/approvals/approvalCard.test.ts b/frontend/src/components/approvals/approvalCard.test.ts index fa91ee825..4a0e29909 100644 --- a/frontend/src/components/approvals/approvalCard.test.ts +++ b/frontend/src/components/approvals/approvalCard.test.ts @@ -77,6 +77,9 @@ describe('the approval card', () => { expect(latestReplied([reading, taken], 's1', new Set())?.id).toBe('c'); expect(latestReplied([reading, taken], 's1', new Set(['r2']))?.id).toBe('b'); expect(latestReplied([reading, taken], 'other', new Set())).toBeUndefined(); + // --update clears the reply it answered: the line still says it came back ready again. + const ready = { ...q20, id: 'd', readyAgain: 30 }; + expect(latestReplied([reading, taken, ready], 's1', new Set())?.id).toBe('d'); }); it('recommended picks start selected', () => { diff --git a/frontend/src/components/approvals/approvalCard.ts b/frontend/src/components/approvals/approvalCard.ts index 6e20eff36..4477e44db 100644 --- a/frontend/src/components/approvals/approvalCard.ts +++ b/frontend/src/components/approvals/approvalCard.ts @@ -172,7 +172,7 @@ function originPanel(item: NotifyItem): string | undefined { /** "doozy-overhaul asks · relayed · Doozy · tylers-mac-mini": who asked, from where. */ export function askedBy(item: NotifyItem): string[] { const parts = [`${item.source.session?.orchestrator ? `${item.source.session.name} orchestrator` : item.source.name} asks`]; - if (item.relayedBy || item.originKind === 'worker') parts.push('relayed'); + if (item.relayedBy) parts.push(`relayed by ${item.relayedBy.name}`); const session = item.source.session?.name ?? (item.connected?.kind === 'session' ? item.connected.name : undefined); if (session && !item.source.session?.orchestrator) parts.push(session); if (item.machine) parts.push(item.machine); @@ -224,9 +224,16 @@ export function bridgedFrom(item: NotifyItem, chatPanelId: string | undefined): * most recently, while its reply is being read, asked about or came back ready again, or once it * was taken as the answer (until the person dismisses it). */ +/** The key ReplyStatusLine dismisses a line by: the reply, or the ready-again version. */ +export function statusKey(item: NotifyItem): string { + return item.reply?.id ?? `ready:${item.id}:${item.readyAgain ?? ''}`; +} + export function latestReplied(items: readonly NotifyItem[], sessionId: string, dismissed: ReadonlySet<string>): NotifyItem | undefined { + // An --update clears the reply it answers, so a ready-again approval is found by readyAgain alone. + const key = statusKey; return items - .filter(item => item.reply && !dismissed.has(item.reply.id) && (item.source.session?.id === sessionId || (item.connected?.kind === 'session' && item.connected.id === sessionId))) + .filter(item => (item.reply || item.readyAgain !== undefined) && !dismissed.has(key(item)) && (item.source.session?.id === sessionId || (item.connected?.kind === 'session' && item.connected.id === sessionId))) .filter(item => (item.state === 'waiting' && (item.reply?.state === 'reading' || item.reply?.state === 'question' || item.reply?.state === 'unread' || item.readyAgain !== undefined)) || (item.state === 'answered' && item.reply?.state === 'answer')) .sort((a, b) => (b.readyAgain ?? b.reply?.at ?? 0) - (a.readyAgain ?? a.reply?.at ?? 0))[0]; diff --git a/main/src/ipc/runpane.ts b/main/src/ipc/runpane.ts index 3731d4b0d..c95d1d47e 100644 --- a/main/src/ipc/runpane.ts +++ b/main/src/ipc/runpane.ts @@ -1907,7 +1907,7 @@ export function registerRunpaneHandlers( } const report = normalizeAgentReport(normalized, new Date().toISOString()); // A blocked report becomes an approval card: it takes the card check before it's recorded (P3). - const warnings = report.state === 'blocked' && report.question ? workerCard(report.summary, report.question).warnings : []; + const warnings = report.state === 'blocked' && report.question ? workerCard(report.summary, report.question, { summaryPath: report.summaryPath }).warnings : []; if (panel.type === 'chat') getChatTabManager().setReport(panel.id, report); else await storePanelAgentReport(panel, report); const panelSummary = panelToSummary(panelManager.getPanel(panel.id) ?? panel); diff --git a/main/src/services/notify/cardCheck.ts b/main/src/services/notify/cardCheck.ts index 0999a8479..c841950a0 100644 --- a/main/src/services/notify/cardCheck.ts +++ b/main/src/services/notify/cardCheck.ts @@ -66,14 +66,14 @@ export function checkCard(card: CardFields): CardCheck { return { refusal: { code: 'ERR_NOTIFY_TITLE', message: 'The title has a line break, so the card was not posted. A title is one line saying what you need.', - next: 'Keep the first line as --title (about 80 to 100 characters) and move the rest to --context, the one- or two-sentence subtitle; put each question in --questions or --choices.', + next: 'Keep the first line as the title (--title, about 80 to 100 characters) and move the rest to the context (--context), the one- or two-sentence subtitle; put each question in questions or choices (--questions, --choices).', }, warnings: [] }; } if (titleLength > CARD_TITLE_REFUSE) { return { refusal: { code: 'ERR_NOTIFY_TITLE', message: `The title is ${titleLength} characters, past the ${CARD_TITLE_REFUSE} a card takes, so it was not posted.`, - next: 'Shorten --title to about 80 to 100 characters saying what you need, put why it matters in --context (one or two sentences), each question in --questions or --choices, and files in --links.', + next: 'Shorten the title (--title) to about 80 to 100 characters saying what you need, put why it matters in the context (--context, one or two sentences), each question in questions or choices (--questions, --choices), and files in links (--links).', }, warnings: [] }; } for (const question of asked(card)) { diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index 593b0a8bd..931efad89 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -1,3 +1,4 @@ +import fs from 'fs'; import { app, BrowserWindow, globalShortcut } from 'electron'; import { AUTOPILOT_OWNER_PANE_PREFIX } from '../../../../shared/types/autopilot'; import { randomUUID } from 'crypto'; @@ -240,7 +241,7 @@ export class NotifyService { const session = this.deps.sessionRecord?.(entry.sessionId); // The status file's needs_you, never its JSON: the question's why, its options and files. // Tidied when the question is an old paragraph (P8); a report recorded before the check stays as sent. - const card = workerCard(entry.report?.summary, question, { quietly: true }); + const card = workerCard(entry.report?.summary, question, { quietly: true, summaryPath: entry.report?.summaryPath }); // D-81: the worker's status file, which its report named, opens beside the card. const statusLink = entry.report?.summaryPath ? [{ label: `${entry.paneName} status`, path: entry.report.summaryPath }] : []; const links = [...statusLink, ...(card.links ?? []).filter(link => !('path' in link) || link.path !== entry.report?.summaryPath)].slice(0, NOTIFY_LINKS_MAX); @@ -776,6 +777,23 @@ export function summaryFiles(summaryPath: string | undefined, needsYouPage: stri if (summaryPath && /\.html?$/iu.test(summaryPath)) return { summaryPage: summaryPath }; const files: WorkerSummaryFiles = {}; if (summaryPath) files.statusFile = summaryPath; - if (needsYouPage) files.summaryPage = path.isAbsolute(needsYouPage) || !summaryPath ? needsYouPage : path.resolve(path.dirname(summaryPath), needsYouPage); + if (needsYouPage) files.summaryPage = path.isAbsolute(needsYouPage) || !summaryPath ? needsYouPage : besideStatusFile(summaryPath, needsYouPage); return files; } + +/** + * A relative `summary` path, as a worker wrote it: from its working folder (usually the worktree, + * an ancestor of its status file) or from the status file's own folder. The nearest folder up from + * the status file that holds it wins; when none does, it reads from the status file's folder. + */ +function besideStatusFile(statusFile: string, page: string): string { + let folder = path.dirname(statusFile); + for (let depth = 0; depth < 6; depth += 1) { + const candidate = path.resolve(folder, page); + if (fs.existsSync(candidate)) return candidate; + const parent = path.dirname(folder); + if (parent === folder) break; + folder = parent; + } + return path.resolve(path.dirname(statusFile), page); +} diff --git a/main/src/services/notify/summaryPage.test.ts b/main/src/services/notify/summaryPage.test.ts index 8283eb7f9..09cb16028 100644 --- a/main/src/services/notify/summaryPage.test.ts +++ b/main/src/services/notify/summaryPage.test.ts @@ -49,12 +49,16 @@ describe("an approval's Summary page", () => { it("falls back to the template, saying so, when the asker's page is gone", () => { const page = summaryPage({ ...q80, summaryPage: path.join(dir, 'removed.html') }); expect(page.kind).toBe('template'); - expect(page.kind === 'template' && page.html).toContain('removed.html, is gone'); + expect(page.kind === 'template' && page.html).toContain('wasn’t found at'); }); it("takes a worker's .html --summary-file as its page, and any other file as its status file", () => { expect(summaryFiles('/w/Q-80-summary.html', undefined)).toEqual({ summaryPage: '/w/Q-80-summary.html' }); expect(summaryFiles('/w/status.json', 'pages/summary.html')).toEqual({ statusFile: '/w/status.json', summaryPage: '/w/pages/summary.html' }); + // Written from the worker's folder (the worktree), with the status file in tmp/: found one folder up. + fs.mkdirSync(path.join(dir, 'tmp'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'tmp', 'summary.html'), '<p>Q-80</p>'); + expect(summaryFiles(path.join(dir, 'tmp', 'status.json'), 'tmp/summary.html').summaryPage).toBe(path.join(dir, 'tmp', 'summary.html')); expect(summaryFiles(undefined, undefined)).toEqual({}); }); }); diff --git a/main/src/services/notify/summaryPage.ts b/main/src/services/notify/summaryPage.ts index f960ad42c..b47dc122f 100644 --- a/main/src/services/notify/summaryPage.ts +++ b/main/src/services/notify/summaryPage.ts @@ -126,7 +126,7 @@ function askerPage(file: string): NotifySummaryPage { try { stat = fs.statSync(file); } catch { - return { kind: 'missing', path: file, message: `The Summary page ${path.basename(file)} is gone: there is no file at ${file} any more, so Pane can't show it. Its worktree may have been removed.` }; + return { kind: 'missing', path: file, message: `The Summary page ${path.basename(file)} wasn't found at ${file}, so Pane can't show it. Its worktree may have been removed, or the path is wrong.` }; } if (!stat.isFile()) return { kind: 'missing', path: file, message: `${file} is not a file, so Pane can't show it as the Summary.` }; if (stat.size > PAGE_LIMIT_BYTES) return { kind: 'missing', path: file, message: `${path.basename(file)} is larger than 4 MiB, too large to show here. Open it from References instead.` }; @@ -139,7 +139,7 @@ export function summaryPage(item: NotifyItem, theme: SummaryTheme = 'light'): No const page = askerPage(item.summaryPage); // A page that's gone falls back to the template, which says so in its footer's place. if (page.kind !== 'missing') return page; - return { kind: 'template', html: summaryTemplate(item, theme).replace('(no Summary was sent)', `(the Summary page it sent, ${escape(path.basename(item.summaryPage))}, is gone)`) }; + return { kind: 'template', html: summaryTemplate(item, theme).replace('(no Summary was sent)', `(the Summary page it sent wasn’t found at ${escape(item.summaryPage)})`) }; } return { kind: 'template', html: summaryTemplate(item, theme) }; } diff --git a/main/src/services/notify/workerContext.test.ts b/main/src/services/notify/workerContext.test.ts index 2c05508ca..783e991bc 100644 --- a/main/src/services/notify/workerContext.test.ts +++ b/main/src/services/notify/workerContext.test.ts @@ -118,3 +118,10 @@ describe("a worker's card as runpane report posts it", () => { expect(workerCard(status, 'x'.repeat(200), { quietly: true }).title).toBe('x'.repeat(200)); }); }); + +describe("a worker's .html --summary-file", () => { + it('is its Summary page, never its subtitle', () => { + const card = workerCard('<!doctype html><html><body><h1>Q-80</h1></body></html>', 'Q-80: Sign in to gcloud?', { summaryPath: '/w/Q-80-summary.html' }); + expect(card.context).toBeUndefined(); + }); +}); diff --git a/main/src/services/notify/workerContext.ts b/main/src/services/notify/workerContext.ts index fe7a256ac..a58ebe5ac 100644 --- a/main/src/services/notify/workerContext.ts +++ b/main/src/services/notify/workerContext.ts @@ -216,8 +216,9 @@ export function workerReportCard(summary: string | undefined, question: string): * old paragraph, and checked. Throws the check's refusal (a PaneError); `quietly` keeps a card that * fails as it was sent instead, for reports recorded before the check existed. */ -export function workerCard(summary: string | undefined, question: string, options: { quietly?: boolean } = {}): StatusCard & { title: string; asSent?: AdmittedCard['asSent']; warnings: string[] } { - const card = workerReportCard(summary, question); +export function workerCard(summary: string | undefined, question: string, options: { quietly?: boolean; summaryPath?: string } = {}): StatusCard & { title: string; asSent?: AdmittedCard['asSent']; warnings: string[] } { + // An .html passed as --summary-file is the card's Summary page, never its subtitle. + const card = options.summaryPath && /\.html?$/iu.test(options.summaryPath) ? {} : workerReportCard(summary, question); const fields = { title: question, context: card.context, choices: card.choices, questions: card.questions, links: card.links }; let admitted: AdmittedCard; try { @@ -227,5 +228,7 @@ export function workerCard(summary: string | undefined, question: string, option return { ...card, title: question, warnings: [] }; } const { title, context, choices, questions, links } = admitted.card; - return { title, context, choices, questions, links, summaryPage: card.summaryPage, asSent: admitted.asSent, warnings: admitted.warnings }; + // A status file's options are plain labels, with no field for a consequence: that warning is no use to a worker. + const warnings = admitted.warnings.filter(warning => !warning.startsWith('Some options have no consequence')); + return { title, context, choices, questions, links, summaryPage: card.summaryPage, asSent: admitted.asSent, warnings }; } diff --git a/packages/runpane-py/src/runpane/generated_contract.py b/packages/runpane-py/src/runpane/generated_contract.py index 0e8bcafbd..c8a50722e 100644 --- a/packages/runpane-py/src/runpane/generated_contract.py +++ b/packages/runpane-py/src/runpane/generated_contract.py @@ -1,4 +1,4 @@ # Generated by scripts/generate-runpane-contract.js. Do not edit by hand. import json -RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \"^22.23.4 || ^24.22.1 || ^26.12.1\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false,\n \"watch\": {\n \"follow\": {\n \"summary\": \"What `runpane watch --follow` does with no other flags: quiet, batched updates and expiry after 3 hours with nothing happening. Individual duration flags override the defaults.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.ready\",\n \"agent.blocked\",\n \"agent.idle\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"pane.gone\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\"\n ],\n \"readySettle\": \"3m\",\n \"blockedSettle\": \"30s\",\n \"minInterval\": \"10m\",\n \"idleAfter\": \"10m\",\n \"idleBackoff\": true,\n \"expireAfter\": \"3h\",\n \"heartbeat\": \"off\",\n \"includeShells\": false,\n \"heldInputDetection\": true\n },\n \"untilEvent\": {\n \"summary\": \"What `runpane watch --until-event` does with no other flags: waits for the first event worth waking an agent for, prints it immediately without follow settling or batching, and exits 0.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.blocked\",\n \"panel.exited\",\n \"pane.gone\"\n ]\n }\n }\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\",\n \"cursor\",\n \"opencode\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n },\n \"cursor\": {\n \"title\": \"Cursor\",\n \"command\": \"cursor-agent --force --trust\",\n \"description\": \"Open a Cursor Agent terminal tab and allow the initial input to drive the agent.\"\n },\n \"opencode\": {\n \"title\": \"OpenCode\",\n \"command\": \"opencode --auto\",\n \"description\": \"Open an OpenCode terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"terminalKeys\": {\n \"enter\": \"\\r\",\n \"escape\": \"\\u001b\",\n \"tab\": \"\\t\",\n \"backspace\": \"\",\n \"space\": \" \",\n \"up\": \"\\u001b[A\",\n \"down\": \"\\u001b[B\",\n \"right\": \"\\u001b[C\",\n \"left\": \"\\u001b[D\",\n \"ctrl-c\": \"\\u0003\",\n \"ctrl-d\": \"\\u0004\"\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true,\n \"localControl\": false\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true,\n \"localControl\": false\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update both Pane and the invoked supported CLI through an independent helper.\",\n \"usage\": [\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\"\n ],\n \"target\": \"client\",\n \"localControl\": false\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version [--pane-dir <path>]\",\n \"runpane --version\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, runpane-on-PATH, and remote setup diagnostics.\",\n \"openWorld\": true,\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"runpane doctor --fix [--json] [--pane-dir <path>]\",\n \"runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"usage\": [\n \"runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command <command> [--json]\"\n ],\n \"toolsets\": [\n \"admin\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"usage\": [\n \"runpane mcp [--toolsets <name,...>] [--read-only]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path <path> [--name <name>] [--json] [--yes] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"usage\": [\n \"runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"usage\": [\n \"runpane repos update --repo <selector> [--name <name>] [--build-script <script>] [--run-script <script>] [--archive-script <script>] [--worktree-folder <path>] [--open-ide-command <command>] [--system-prompt <text>] [--env <json>] [--env-remove <name,...>] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos rename --repo <selector> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"usage\": [\n \"runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"usage\": [\n \"runpane secrets set --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"localControl\": true\n },\n {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"usage\": [\n \"runpane secrets delete --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"usage\": [\n \"runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... [--pane-dir <path>] -- <command>...\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"usage\": [\n \"runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"usage\": [\n \"runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace state\",\n \"summary\": \"Read one workspace snapshot of every Pane and CLI panel.\",\n \"usage\": [\n \"runpane workspace state [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceStateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"usage\": [\n \"runpane workspace list [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"usage\": [\n \"runpane workspace enable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"usage\": [\n \"runpane workspace disable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"usage\": [\n \"runpane workspace [<machine>] read <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"usage\": [\n \"runpane workspace [<machine>] write <path> [--content <text>] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"usage\": [\n \"runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"usage\": [\n \"runpane handoff --template\",\n \"runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace agent and Pane transitions using a daemon-held cursor.\",\n \"usage\": [\n \"runpane watch [--follow|--until-event] [--session <id|name>|--pane <id>] [--json] [overrides]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceWaitRequest\",\n \"workspaceWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"usage\": [\n \"runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --from-json <path|-> [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane without changing the worktree.\",\n \"usage\": [\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [--resume <id>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\",\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"usage\": [\n \"runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes pin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"usage\": [\n \"runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"usage\": [\n \"runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"usage\": [\n \"runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels) on explicit user request.\",\n \"usage\": [\n \"runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent or terminal tab inside an existing Pane, sharing its worktree. The default for review, QA, fixes and any other agent on work that already has a Pane.\",\n \"usage\": [\n \"runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"usage\": [\n \"runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"localControl\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"usage\": [\n \"runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--source user|agent] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place, resuming its agent's conversation.\",\n \"usage\": [\n \"runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"usage\": [\n \"runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"usage\": [\n \"runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"usage\": [\n \"runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane panels conversation --hook [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"usage\": [\n \"runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"sessions:get-git-status\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"usage\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stage-and-commit\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"usage\": [\n \"runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-push\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"usage\": [\n \"runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-pull\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"usage\": [\n \"runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:rebase-main-into-worktree\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"usage\": [\n \"runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:restore\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"usage\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:squash-and-rebase-to-main\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash-pop\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"usage\": [\n \"runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-soft-reset\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"usage\": [\n \"runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-fetch\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"usage\": [\n \"runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:run-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"usage\": [\n \"runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:stop-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"usage\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"folders:move-session\",\n \"args\": [\n \"--pane\",\n \"--folder\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"usage\": [\n \"runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"folders:get-by-project\",\n \"args\": [\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"usage\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"folders:create\",\n \"args\": [\n \"--name\",\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"usage\": [\n \"runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \"runpane links create --repo <repo-id> [--json]\",\n \"runpane links create --session <session-id> [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"links\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"usage\": [\n \"runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"runpane:links:open\",\n \"args\": [\n \"--url\"\n ]\n },\n \"toolsets\": [\n \"links\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"usage\": [\n \"runpane docs search --query <text> [--limit <count>] [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"usage\": [\n \"runpane docs read --doc <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"usage\": [\n \"runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"usage\": [\n \"runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"usage\": [\n \"runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"usage\": [\n \"runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"usage\": [\n \"runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \"runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"runpane notify --resume <id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"usage\": [\n \"runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"usage\": [\n \"runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"usage\": [\n \"runpane autopilot status [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"usage\": [\n \"runpane autopilot waiting [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"usage\": [\n \"runpane autopilot stack [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"usage\": [\n \"runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"usage\": [\n \"runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"usage\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"usage\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"usage\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"usage\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"usage\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"usage\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"usage\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"usage\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"usage\": [\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"usage\": [\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"usage\": [\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"usage\": [\n \"runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"usage\": [\n \"runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"usage\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"usage\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"usage\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"usage\": [\n \"runpane loops list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"usage\": [\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"usage\": [\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"usage\": [\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"usage\": [\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"localControl\": true\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository, created/renamed pane session, named lock, or loop.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name. For a loop, the worktree and branch name template; defaults to loop-<name>.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"description\": \"Exact new branch name for the created worktree, slashes included. Must not exist yet; defaults to the worktree name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"aliases\": [\n \"--base\"\n ],\n \"description\": \"Ref the created worktree branches from. --base is an alias.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"description\": \"Top-level sidebar folder for an adopted pane.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<agent-session-id>\",\n \"description\": \"Agent session id to persist and resume in an adopted pane.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"description\": \"http(s) or file URL for panels open; pane:// link for links open.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"description\": \"File inside the Pane worktree to open; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"aliases\": [\n \"--prompt-file\"\n ],\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--attach\",\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a structured JSON request payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create/adopt --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated. On panels conversation it is instead what started the conversation: startup, resume, clear, or compact.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"description\": \"Use a persisted daemon-held workspace cursor.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"description\": \"Read workspace events after this generation.\"\n },\n {\n \"name\": \"--from\",\n \"value\": \"<now|earliest>\",\n \"description\": \"Choose where a new named cursor starts.\"\n },\n {\n \"name\": \"--kinds\",\n \"value\": \"<kind,...>\",\n \"description\": \"Limit workspace events to comma-separated kinds.\"\n },\n {\n \"name\": \"--name-contains\",\n \"value\": \"<text>\",\n \"description\": \"Limit workspace events to Pane names containing text.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane from workspace watch results; repeatable.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"description\": \"Print a HEARTBEAT line this often under --follow (a duration; default off).\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"description\": \"First IDLE reminder after a READY panel stays idle (a duration; default 10m under --follow; 0 turns IDLE off).\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"description\": \"End a --follow watch after this long with nothing but IDLE, printing WATCH EXPIRED and exiting 0 (a duration; default 3h; 0 never expires).\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Quiet time before READY under --follow (a duration; default 3m).\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Time before BLOCKED under --follow (a duration; default 30s).\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"description\": \"Batch window for non-urgent lines under --follow (a duration; default 10m).\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read diagnostic report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"description\": \"Named orchestration Session id or exact name.\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"description\": \"How long a named lock is held before it expires: a number with ms, s, m, or h (such as 30m); a bare number is milliseconds. At least 1s, at most 24h.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"description\": \"lock acquire: how long to wait for a held lock to come free; 0 (the default) returns at once. On panels submit, panels create and panes create, --wait takes no value: block until that panel reports, misses its report, waits on a prompt, or exits, then print one JSON result.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"description\": \"With --wait: give up after this long (30m, 2h; 0 waits without limit) and exit 124.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"description\": \"What a named lock is for. Outside a Pane terminal it also names the owner, so pass the same note to release it.\"\n },\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"description\": \"Five-field cron schedule in this machine's local time zone, such as \\\"0 3 * * *\\\". Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"description\": \"Commit message for panes commit.\"\n },\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"description\": \"Search words for docs search.\"\n },\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"description\": \"Doc path for docs read.\"\n },\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"description\": \"MCP toolsets for runpane mcp.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"description\": \"Named keys for panels input, such as down,enter.\"\n },\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"description\": \"Report state for runpane report.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"description\": \"Pull request number for runpane report.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"description\": \"Head commit (7-40 hex characters) for runpane report.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"description\": \"Report summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read the report summary from a file or stdin.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"description\": \"What a blocked worker needs answered; required with --state blocked.\"\n },\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"description\": \"The conversation (agent session) id a panel's agent is on, for runpane panels conversation.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"description\": \"Working directory on the target machine for workspace exec.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"description\": \"Handoff note to check and send; - reads stdin.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"description\": \"Handoff destination machine; overrides the machine in the destination text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"description\": \"Model for the receiving agent; overrides the destination text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"description\": \"Reasoning effort for the receiving agent (minimal, low, medium, high, xhigh, max); overrides the destination text.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository. Defaults to worktrees/ in the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME|json>\",\n \"description\": \"secrets exec: inject secret NAME as VAR; repeatable. repos update: a JSON array of project variables to add or replace by key.\"\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"description\": \"Pane secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"description\": \"Secrets to inject, comma-separated. Defaults to every secret in the project.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"description\": \"secrets request: why you need the secret, shown on the card. autopilot close: why it closes, recorded on the item.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"description\": \"notify: what to show: pick one of choices, approve or deny, type an answer, or an update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"description\": \"notify: background shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options: 1 to 9 choices as a JSON array of {label, detail?, recommended?}; with --update a reworded choice adds \\\"was\\\", its old label.\"\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: a preformatted block, such as the command or SQL to approve.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: label of the disclosure that reveals --detail, such as Show the SQL; up to 40 characters.\"\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui ask: suggested answers as a JSON array of strings.\"\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui info: one button as a JSON object {label, url}.\"\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"description\": \"notify: up to 6 related files, pages or approvals as a JSON array of {label, path}, {label, url} or {label, approval}; any ui.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"description\": \"Text that workspace write writes instead of stdin.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"description\": \"Start an Agent Farm profile (such as greenfield/implementer:opus) in a chat tab.\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"description\": \"A launch argument for --agent-farm, shown to the agent in its LAUNCH CONTEXT; repeatable.\"\n },\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"description\": \"autopilot stack push only: the waiting item (an id from `runpane autopilot waiting`) to put on the stack.\"\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"description\": \"notify only: relay into this waiting item (an id from `runpane inbox`): the same card gains the choices, context and links, and the call waits on it.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"description\": \"notify only, for Autopilot's own items: the Session the item is about.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options, instead of --choices: 2 to 4 questions as a JSON array of {title, choices}, each with 1 to 9 choices and one pick.\"\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: say how you took the person's reply to this approval; answer or question follows the id.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"description\": \"notify --took: the word after the id, as a flag (the MCP tool's tookAs).\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"description\": \"notify --took <id> question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: save a new version of this approval after the person's question, and wait on it.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: what changed, one line per change.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"description\": \"inbox ack only: record this D-n on each named item.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"description\": \"autopilot answer only: answer with this choice's label.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"description\": \"autopilot answer only: why, recorded on the item: a D-n, a document or the person's words.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--split\",\n \"description\": \"Open the tab in split view beside the primary tab group (default for panels open).\"\n },\n {\n \"name\": \"--tab\",\n \"description\": \"Open the tab in the current tab group instead of split view. On autopilot stack push it takes a value instead: --tab <path-or-url> adds a tab (a file, or an http(s) URL); repeatable.\"\n },\n {\n \"name\": \"--pinned\",\n \"description\": \"Request pinning at creation; Session association still clears the first pin. Outside a Session, creation pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"description\": \"Create the pane unpinned instead of the default pinned (the UI's favorite/pin star).\"\n },\n {\n \"name\": \"--no-associate\",\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session.\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn, or a tab holds unsent input; for panels close, close the tab even if an agent opened it, its agent is busy, or it holds unsent input (a stuck tab); for lock release, release another owner's lock.\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"description\": \"Also check and remove an adopted Pane's worktree when archiving it. The local branch is kept.\"\n },\n {\n \"name\": \"--merged\",\n \"description\": \"With panes archive --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work. Other Panes are skipped with a reason (pr-open, pr-not-merged, pr-status-unknown, or a local safety code).\"\n },\n {\n \"name\": \"--closed\",\n \"description\": \"With panes archive --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n {\n \"name\": \"--launch\",\n \"description\": \"Run an adopted pane's agent resume command immediately instead of staging it.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--follow\",\n \"description\": \"Continue waiting for workspace events until interrupted.\"\n },\n {\n \"name\": \"--until-event\",\n \"description\": \"Watch only: wait across daemon calls for the first matching event, print it, and exit 0. --timeout-ms becomes the total deadline (exit 124). For harnesses that wake an agent when a process exits.\"\n },\n {\n \"name\": \"--ack-now\",\n \"description\": \"Advance a named cursor immediately for at-most-once delivery.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"description\": \"Include up to 120 characters of unsubmitted composer text in ready events.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"description\": \"Resume every stopped agent panel in every non-archived Pane.\"\n },\n {\n \"name\": \"--include-shells\",\n \"description\": \"Include ordinary shell panels in follow mode.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"description\": \"Disable redacted held-input detection under --follow (STUCK lines, and heldInputPresent in JSON).\"\n },\n {\n \"name\": \"--self-test\",\n \"description\": \"Probe the current daemon watch path without advancing a named cursor.\"\n },\n {\n \"name\": \"--report\",\n \"description\": \"Prepare a redacted doctor report; external filing still requires --yes.\"\n },\n {\n \"name\": \"--fix\",\n \"description\": \"Update each npm-installed runpane older than the Pane-managed CLI to its version. Never removes a file.\"\n },\n {\n \"name\": \"--read-only\",\n \"description\": \"Serve only read-only MCP tools.\"\n },\n {\n \"name\": \"--template\",\n \"description\": \"Print the handoff note template, with this checkout's (or Session's) origin and git state filled in.\"\n },\n {\n \"name\": \"--push\",\n \"description\": \"Before a handoff, commit uncommitted work as WIP and push the branch (never forces).\"\n },\n {\n \"name\": \"--note-only\",\n \"description\": \"Hand off to a fresh agent with the note, instead of carrying and resuming your own session.\"\n },\n {\n \"name\": \"--interrupt\",\n \"description\": \"Stop the current agent turn, wait for an empty idle composer, then submit and verify the replacement.\"\n },\n {\n \"name\": \"--expect-report\",\n \"description\": \"Pane expects a `runpane report` and emits agent.report.missing if the agent goes ready or exits without one. The default for work an agent sends; use it to ask for one on work marked --source user.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"description\": \"Send work without waiting for a `runpane report` (fire-and-forget input); no agent.report.missing follows.\"\n },\n {\n \"name\": \"--all\",\n \"description\": \"inbox ack only: clear every item of the --session.\"\n },\n {\n \"name\": \"--paused\",\n \"description\": \"loops create only: create the loop paused.\"\n },\n {\n \"name\": \"--chat\",\n \"description\": \"Open the agent as a chat tab (Claude or Codex over ACP, no terminal) instead of a terminal tab.\"\n },\n {\n \"name\": \"--next\",\n \"description\": \"autopilot stack push only: put the entry right after the one on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--approve\",\n \"description\": \"autopilot answer only: approve the item.\"\n },\n {\n \"name\": \"--deny\",\n \"description\": \"autopilot answer only: deny the item.\"\n },\n {\n \"name\": \"--hook\",\n \"description\": \"runpane panels conversation: read a Claude Code SessionStart hook's JSON from stdin and report it for PANE_PANEL_ID. Prints nothing and always exits 0.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane sessions list [--json] [--pane-dir <path>]\",\n \" runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions pause|resume --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \" runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane loops list [--json]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read|write|exec|<command>\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \" --report Prepare a redacted, inspectable report\",\n \" --body-file <path|-> Read report evidence from a file or stdin\",\n \" --title <text> Report title\",\n \" --yes Confirm creating one greenfield-inc/Pane issue\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Remote daemon data directory (default: ~/.pane_remote)\",\n \" --pane-path <path> Existing Pane executable used to perform repair\",\n \" --yes Skip the restart confirmation\",\n \" --json Print one machine-readable repair result\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Repository display name\",\n \" --build-script <script> Build (setup) script; runs in each new worktree\",\n \" --run-script <script> Run script; runs on the Play button\",\n \" --archive-script <script> Archive script; runs before a worktree is removed\",\n \" --worktree-folder <path> Folder for new worktrees, absolute or relative to the repository\",\n \" --open-ide-command <command> Command the Open in IDE button runs\",\n \" --system-prompt <text> Project system prompt\",\n \" --env <json> Project variables to add or replace, a JSON array: [{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://...\\\"}, {\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]\",\n \" --env-remove <name,...> Project variables to remove\",\n \" --clear <setting,...> Clear settings, e.g. run-script,archive-script; cleared scripts fall back to the repo's config file\",\n \" --from-json <path|-> Settings object: {\\\"runScript\\\": \\\"pnpm dev\\\", \\\"archiveScript\\\": null, \\\"envVars\\\": {\\\"set\\\": [...], \\\"remove\\\": [\\\"OLD\\\"]}, ...}; flags override it\",\n \" --dry-run Show the result without saving\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Limit results to one saved repository\",\n \" --pane <pane-id> Return one Pane, including an idle Pane with zero usage\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read <path>\",\n \" runpane workspace <machine> write <path> (content from --content or stdin)\",\n \" runpane workspace <machine> exec -- <command>\",\n \" runpane workspace <machine> <runpane command> (any command that talks to Pane, e.g. sessions list --json)\",\n \" runpane workspace enable|disable\",\n \"\",\n \"<machine> is a Tailscale name, unique prefix, or IP. Your own machines are reachable, and a teammate's machine when its owner set Who can connect to Everyone on tailnet.\",\n \"Without <machine>, read, write, and exec route a path that cannot exist here (C:\\\\... on a Mac) to the one joined machine it fits.\",\n \"Commands that only run here (doctor, agent-context, mcp, docs) run there with: runpane workspace <machine> exec -- 'runpane doctor'\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes, a busy agent, or unsent input\",\n \" --remove-worktree Also check and remove an adopted Pane's worktree (the branch is kept)\",\n \" --session <id|name> Archive the Panes associated with this Session (requires --merged)\",\n \" --merged Only archive Panes that are clean and pushed, or merged via a PR\",\n \" --closed With --session --merged, also archive Panes whose PR closed unmerged\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Refresh and print archive safety evidence without archiving\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to pin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without pinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to unpin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without unpinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to rename\",\n \" --name <new-name> New non-empty Pane/session name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without renaming the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to focus\",\n \" --panel <panel-id> Optional panel/tab id inside the Pane to select\",\n \" --source <user|agent> Mutation source; does not change focus behavior\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Accepted and ignored; agent-context never contacts Pane\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \"\",\n \"Runs the Pane MCP server over stdio. Each runpane command with a JSON result becomes a tool that returns the same JSON as `runpane <command> --json`. Commands that need --yes take `yes: true` instead.\",\n \"Pane registers this server with Claude Code, Codex, and Cursor automatically (Settings > AI & Agents). To register it by hand:\",\n \" claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \" codex mcp add pane -- npx --yes runpane@latest mcp\",\n \"\",\n \"The MCP server ships in the npm package and inside the Pane app. The Python package does not include it.\",\n \"\",\n \"Options:\",\n \" --toolsets <name,...> core (default), all, read, repos, panes, panels, agents, sessions, workspace, git, docs, links, admin\",\n \" --read-only Serve only read-only tools\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tab to close\",\n \" --dry-run Report whether the tab would close, and why not, without closing it\",\n \" --force Close it even if an agent didn't open it, its agent is busy, or it holds unsent input\",\n \" --source <user|agent> Mark mutation source\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"\",\n \"Options:\",\n \" --query <text> Words to search for.\",\n \" --limit <count> Maximum results; defaults to 5.\",\n \" --json Print machine-readable output.\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"Read one Pane doc, help topic, or skill in full.\",\n \"\",\n \"Options:\",\n \" --doc <path> Path from docs search, such as docs/PANE_MCP.md.\",\n \" --json Print machine-readable output.\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --name <name> Name for the new Pane.\",\n \" --agent <codex|claude|cursor> Built-in agent to launch.\",\n \" --tool-command <command> Custom terminal command instead of a built-in agent.\",\n \" --prompt <task> The task to send once the agent is ready.\",\n \" --base-branch <branch> Branch to start the worktree from.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --limit <count> Screen lines to return; defaults to 40.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --text <message> The message to submit.\",\n \" --as-file-pointer Write the message to a prompt file and send `Read and follow <path>`.\",\n \" --no-expect-report Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" runpane loops list [--json] [--pane-dir <path>]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form; ~ is that machine's home.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form.\",\n \" --content <text> Text that replaces the file. Without it, stdin is written.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" --cwd <path> Working directory on that machine, in any path form; defaults to its home.\",\n \" --timeout-ms <milliseconds> Stop the command after this long (default 120000, at most 540000).\",\n \" --json Print machine-readable output.\",\n \" -- <command> The command line, run by that machine's shell.\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"Hand your task to another agent. You write the note (runpane handoff --template); the CLI checks it,\",\n \"makes sure your branch is pushed, sends the note to the destination, and starts the agent there.\",\n \"From a Pane tab, a handoff to the same agent (Claude, Codex, Cursor or OpenCode) carries your own session:\",\n \"another machine resumes a copy of it, this machine resumes a fork. The CLI then checks the receiver\",\n \"continued it (continued, inconclusive, or not-continued, which exits 1 and keeps the receiver).\",\n \"--note-only, another agent, or no session to carry starts a fresh agent with the note instead.\",\n \"From a Session's terminal it skips git and creates the Session on the other machine; a same-agent\",\n \"Session resumes your conversation when first opened, otherwise it reads the note. The sender Session stays.\",\n \"\",\n \"Options:\",\n \" <destination> Freeform: agent, model, effort, and machine, such as \\\"codex gpt-5 high on parsas-macbook-pro\\\".\",\n \" --template Print the note template, with this checkout's (or Session's) origin and git state filled in.\",\n \" --note-file <path|-> The note to check and send; every template section must be filled in.\",\n \" --machine <name> Destination machine (Tailscale name or unique prefix); overrides the text.\",\n \" --agent <codex|claude|cursor> Receiving agent; overrides the text.\",\n \" --model <model> Model for the receiving agent; overrides the text.\",\n \" --effort <level> minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\",\n \" --repo <selector> Saved repository on the destination; defaults to the one with the same remote.\",\n \" --push Commit uncommitted work (not the note) as WIP and push the branch first (never forces).\",\n \" --note-only Start a fresh agent with the note instead of carrying your own session.\",\n \" --dry-run Check destination reachability, saved repo and OS; print note and git state; send nothing. From a Session: the name it would get.\",\n \" --json Print machine-readable output.\",\n \"\",\n \"Examples:\",\n \" runpane handoff --template > ~/handoff.md\",\n \" runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \" runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" python -m runpane loops list [--json]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" python -m runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \" --report\",\n \" --body-file <path|->\",\n \" --title <text>\",\n \" --yes\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --yes\",\n \" --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --name <name>\",\n \" --build-script <script>\",\n \" --run-script <script>\",\n \" --archive-script <script>\",\n \" --worktree-folder <path>\",\n \" --open-ide-command <command>\",\n \" --system-prompt <text>\",\n \" --env <json>\",\n \" --env-remove <name,...>\",\n \" --clear <setting,...>\",\n \" --from-json <path|->\",\n \" --dry-run\",\n \" --yes\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" python -m runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" python -m runpane secrets list [--project <project>] [--json]\",\n \" python -m runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" python -m runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | python -m runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" python -m runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \"\",\n \"runpane workspace list|read|write|exec|enable|disable ship in the npm package: npx --yes runpane@latest workspace list\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" python -m runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --remove-worktree\",\n \" --session <id|name>\",\n \" --merged\",\n \" --closed\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --name <new-name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --panel <panel-id>\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \" --pane-dir <path> Accepted and ignored\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp\",\n \"\",\n \"The Pane MCP server ships in the npm package and inside the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest mcp\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --dry-run\",\n \" --force\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"python -m runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"python -m runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"python -m runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"python -m runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"python -m runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"python -m runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"python -m runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"python -m runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"`runpane docs search` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs search\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"`runpane docs read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs read\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents start` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents start\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents status` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents status\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents send` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents send\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Why it matters, shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" python -m runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"python -m runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"python -m runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"python -m runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" python -m runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" python -m runpane loops list [--json] [--pane-dir <path>]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"python -m runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace list` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace list\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace enable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace enable\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace disable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace disable\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"`runpane workspace read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace read\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"`runpane workspace write` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace write\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"`runpane workspace exec` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace exec\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"`runpane handoff` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest handoff\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"python -m runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"python -m runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Background shown under Details\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane mcp\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane repos get --repo Pane --json\",\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --worktree-folder ../pane-worktrees --yes --json\",\n \"runpane repos update --repo Pane --clear run-script --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://api.test\\\"},{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\",\n \"runpane repos rename --repo 3 --name Lev --yes --json\",\n \"runpane secrets list --json\",\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes\",\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\",\n \"runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\",\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook tests against Stripe test mode.\\\" --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes cost --pane <pane-id> --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock release --name testing-account --json\",\n \"runpane lock list --json\",\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops list --json\",\n \"runpane loops run --loop <loop-id> --yes --json\",\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\",\n \"runpane agents status --pane <pane-id> --json\",\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane panels last-message --panel <panel-id> --json\",\n \"runpane panels conversation --conversation <id> --source resume --json\",\n \"runpane panels close --panel <panel-id> --yes --json\",\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\",\n \"runpane panes pause --pane <pane-id> --yes --json\",\n \"runpane panes resume --pane <pane-id> --yes --json\",\n \"runpane docs search --query \\\"archive a pane\\\" --json\",\n \"runpane links create --pane <pane-id> --json\",\n \"runpane panes git-status --pane <pane-id> --json\",\n \"runpane help\",\n \"runpane <command> --help\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. The remote-host wizard asks only for a name, then runs interactive Tailscale setup with automatic port selection; explicit install daemon flags remain available for SSH and manual URLs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` defaults to both app and active supported CLI, with independent supervision, staging, durable status, and explicit restoration outcomes. Prerequisites, status fields/exit codes, draft handling and manual recovery are documented in [Update recovery](UPDATE_RECOVERY.md).\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, every `runpane` on `PATH` against the Pane-managed CLI, and remote-daemon hints. `--fix` updates npm installs of `runpane` older than the Pane-managed CLI to its version and never removes a file. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane daemon repair` rewrites and restarts only the managed remote-daemon launcher/service. It never creates pairing credentials, changes tunnels, or downloads Pane; doctor only recommends it and never runs it automatically.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output. An unknown command exits 2 and lists the closest command names; with `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout.\",\n \"`runpane mcp` runs a stdio MCP server whose tools are generated from this contract: every command with result `jsonSchemas` becomes a tool that runs `runpane <command> --json` and returns its output. Only the npm package and the Pane app include it; the Python wrapper prints how to run it with Node and exits non-zero.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane repos get` prints a saved repository's Project Settings. Each script shows its saved value, the fallback from the repository's config file, and the one Pane will run.\",\n \"`runpane repos update` changes a saved repository's Project Settings through the same save as the Project Settings dialog. It requires `--yes`, supports `--dry-run`, clears settings with `--clear`, and adds, replaces or removes project environment variables with `--env` and `--env-remove`.\",\n \"`runpane repos rename` trims and sets a saved repository's display name, the same rename as the repository settings in the app, and returns the updated repository record.\",\n \"`runpane secrets` manages Pane secrets: named values, addressed as `<project>/<NAME>`, that every machine saved in Pane keeps a copy of. `list` shows names, times and machines only. `set` reads the value from stdin. `delete` removes one everywhere. `exec` runs a command with secrets in its environment and returns its exit code. `request` asks the person on a masked Notify card and returns only `saved` or `declined`. No command prints a value, and the MCP server has no tool that sets or reads one. These commands run on the machine itself, so `runpane workspace <machine> secrets ...` is refused; use `runpane workspace <machine> exec -- runpane secrets ...`.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes cost` reports estimated token costs per Pane for the last 30 days, including per-model breakdowns and cache efficiency; unscoped output includes an Unattributed bucket that reconciles against workspace totals.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed. New Panes are pinned into the UI's favorite/pin set by default, except when the CLI runs inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID`), where child worktrees default to unpinned. Explicit `--pinned` / `--no-pinned` override creation defaults. First Session association clears an existing pin, including an explicit creation pin; use `panes pin` after association to pin a child. This avoids duplicating Session children in the global pinned list. Manual pins applied afterward are preserved. Panes created interactively in the Pane UI are unaffected. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"For `panes create --wait-ready` (and `panels create`, `panes adopt --launch`), ready means an identified agent at its own input box: declared with `--agent`, or detected from the command, its foreground process or its screen. A `--tool-command` that never shows a supported agent is not ready: `readiness.ok` is false with `readiness.reason.code` `no-agent-detected` after the timeout, or `command-exited` as soon as the command exits back to the shell, and nothing is typed into it. `agent-not-ready` means the agent was detected but its input box was not ready in time. CLI readiness is confirmed on consecutive polls. Codex also requires a recognized configured model or context footer below its live composer; its provisional startup composer is not ready. Hidden or unrecognized Codex status lines conservatively time out rather than prove readiness. Startup prompts return blocked with the prompt's own text in blocked.message and a suggestedCommand, and Pane never answers them, apart from Claude's folder trust for a Session's own folder: first-run trust and daemon dialogs and Agent Farm's `Trust this repository workspace? [y/N]` as blocked.kind=first-run-dialog; Codex's security setup banner and any other prompt, including a wrapper's or shell's `[y/N]` before the agent appears, as agent-prompt. A starting message blocked this way is held and sent once the prompt is answered and the agent's input box is ready. Every create with a starting message returns `initialInput`. Pane stages the text in the composer, presses the agent's submit key separately (Ctrl+Enter for Codex, declared or detected), and checks where it went: `initialInput.delivery` is `taken` or `queued` (from the agent's transcript or its screen), `in-composer`, or `unknown` (`argv` means a prompt was routed at launch; Pane gives the transcript up to 10 seconds before settling on unknown). `initialInput.verifiedSubmitted` is true exactly when it is `taken` or `queued`, and the create's `ok` is false unless a Claude or Codex starting message is verified. Cursor and OpenCode have no acceptance probe: their delivery stays `unknown` with a delivery.message saying so, `verification: unverifiable`, and `ok` follows readiness. Without `--wait-ready` the create returns at once with `initialInput.verifiedSubmitted: false`, a delivery.message saying the call did not wait, and a `panels wait` nextCommand; Pane still sends the message once the agent is identified and at its input box, and gives up without typing it if no supported agent appears within 30 seconds.\",\n \"`runpane panes archive` refreshes the configured upstream, reports exact unpushed commit evidence, and refuses unsafe archive operations unless `--force` is used. A branch whose upstream is gone counts as pushed when a merged GitHub pull request has HEAD as its head (`safetyCheck.mergedViaPr`). Add `--dry-run` to inspect the same evidence without archiving. `--remove-worktree` applies the same check and removal to an adopted worktree; local branches are always kept. Successful archives report `worktreeCleanup: completed` once the worktree is gone from its path and from git; `trashDeletion: pending` means its files are still being deleted in the background. `runpane panes archive --session <id|name> --merged` archives every Session Pane that is clean and pushed or merged, and reports a reason for each skipped Pane. Archive refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless `--force` is used, and the Session sweep skips it. Add `--closed` to the sweep to also archive Panes whose PR closed without merging.\",\n \"`runpane panes rename` trims and updates a Pane's display name without changing its worktree, branch, panels, or focus, and returns the updated pane summary.\",\n \"`runpane panes focus` raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Because it steals the user's window focus, run it only on an explicit user request to open, focus, show, or switch to a Pane; never focus a Pane proactively, the same doctrine that keeps `panes create` background/no-focus for `--source agent`.\",\n \"`runpane panels open` opens a URL or a file from the Pane worktree as a tab in an existing Pane (default: the calling panel's Pane from `PANE_SESSION_ID`), in split view beside the agent unless `--tab` is passed. Files open in an editor tab with read-only previews for supported formats, and HTML opens as editable source; an existing tab showing the same target is reused. It brings the tab forward for clients already showing that Pane, and never raises or focuses the Pane window or switches anyone's Pane.\",\n \"`runpane panels close --panel <panel-id>` closes a tab exactly like closing it in Pane (a terminal tab's process stops) and never archives the Pane or touches its worktree. Only a tab an agent opened with `--source agent` can close; Pane records that opener, and `panels list` shows it as `openedBy`. It refuses, without closing, the person's tabs and every tab from before Pane recorded openers (`opened-by-user`), permanent tabs (`permanent`), an agent mid-turn or waiting for an answer (`agent-busy`), unsent composer text or unsaved editor edits (`unsent-input`), and a live tab whose input Pane cannot read, such as agents other than Claude and Codex or chat tabs (`input-unknown`). A refusal prints `{ ok: false, closed: false, refused: { code, message, next } }` and exits 1. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports `wouldClose` and any refusal without closing. `--force` closes the tab anyway, as its close button does, for a stuck tab or one the person asked to close (`forced: true`); only a permanent tab still stays.\",\n \"`runpane panels list` lists tool panels inside one Pane session. Each panel with a saved agent session ID reports it as `resumeId`, with `resumeSource` (`agent-farm`, `configured`, or `agent`) naming the rule that reopens the conversation.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"`runpane panes create --branch <name>` creates the Pane's worktree on exactly that new branch, slashes included (for example `agents/w5a`). The name is checked with `git check-ref-format --branch`, creation fails if the branch already exists, and Pane never renames it to make it unique. `--worktree-name` still names the directory and defaults to `--name`. `--base` is an alias for `--base-branch`, and `--prompt-file` for `--initial-input-file`. `panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`. When the base is a remote-tracking branch (the default `origin/main`, or one named with `--base`), Pane fetches it first, waiting at most about 10 seconds, so the Pane starts at the remote's current tip. Each created item reports `base` (`ref`, `commit`, `refresh`); if the fetch fails the Pane still starts, from the local ref, and `base.notice` says so and how to catch up.\",\n \"`runpane panes adopt --launch` accepts `--prompt`, `--prompt-file`, `--wait-ready`, and `--ready-timeout-ms`, and reports `readiness`, `initialInput`, and `nextCommand` like `panes create`. A prompt without `--launch` is an error rather than being dropped.\",\n \"If composer submission cannot be verified without risking a duplicate, the create item is unsuccessful with `initialInput.staged`, `initialInput.attempts`, `initialInput.blocked.kind: submission_unverified`, and an actionable `nextCommand`. The CLI-facing `--prompt` alias maps to this canonical `initialInput` result.\",\n \"When running from WSL while Pane is installed on Windows, a Linux-installed runpane cannot reach Pane: it looks for a missing `/tmp/pane-daemon.../daemon.sock`. Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane` from any WSL shell) runs the Windows CLI through WSL interop and converts path arguments. With a Pane build older than that shim, invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"`runpane watch` waits for agent, Pane, panel, and PR events from the daemon journal without polling. `--follow` keeps watching with defaults tuned for an agent watching unattended (quiet, batched, and it ends itself after 3 hours with nothing happening); individual duration flags override these defaults. Cadence requires a named follow consumer; `--follow --since <generation>` has no consumer name and bypasses settling and batching. `--until-event` waits for one event worth waking for, prints it immediately without follow settling or batching, and exits 0. The defaults are listed in the contract under defaults.watch.\",\n \"A watch releases its pending slot when its client socket or named pipe disconnects. Re-arming the same named cursor takes over the previous request; the superseded watcher exits with a WATCH ERROR instead of competing for the cursor. Anonymous concurrent watchers retain the eight-wait limit. Each request also has a 125-second lease (slightly longer than the maximum 120-second long poll), so an abandoned request is reclaimed even if no disconnect arrives.\",\n \"The desktop app treats one consumer name specially: while a `runpane watch --as dictate` request is in flight, and for 30 seconds after the last one ends, Pane skips its own \\\"needs your input\\\" and \\\"finished\\\" desktop notifications, because [Dictate](https://github.com/parsakhaz/open-dictate) shows those events on its pill. When that watcher stops, Pane's notifications come back. The CLI's flags and output are unchanged.\",\n \"`sessions list` list durable named orchestration Sessions.\",\n \"`sessions create` create a durable named orchestration Session and its hidden terminal owner.\",\n \"`sessions get` read one durable named orchestration Session.\",\n \"`sessions update` update a Session overview from structured JSON.\",\n \"`sessions pin` and `sessions unpin` set the Session pin star through the existing UI update path. Create JSON accepts `isPinned` (default false); update JSON preserves the pin when the field is omitted.\",\n \"`sessions set-agent` switch the durable terminal agent for a named Session.\",\n \"`sessions associate` associate a user-visible Pane with a named Session.\",\n \"`sessions detach` detach a Pane from a named Session.\",\n \"`sessions overview` read a live status, activity, git, and pull request overview for a named Session. Each panel in `panes[].panels[]` carries its own latest `runpane report` as `report`, so one call shows what every worker last reported and when (`reportedAt`, next to the panel's `state` and `lastActivityAt`). When a report's summary is a complete JSON object, such as a status file sent with `--summary-file`, `report.data` holds it exactly as the worker gave it; Pane never reads, renames or validates its fields. A summary that is not a JSON object (plain text, an array, a scalar or invalid JSON), is nested more than 64 levels deep, or was truncated (`summaryTruncated`), has no `data`. A panel that never reported has no `report`. `panes[].report` is still the newest report among the Pane's panels. Each panel also has `type` (`terminal`, `browser`, `editor`, `explorer`, `diff`, ...): a Pane linked whole lists every tab, with `explorer` and `diff` last, and a link that names tabs lists exactly those.\",\n \"`runpane lock acquire|release|list` coordinate a resource shared between agents, such as one test account. The caller's Pane and panel own the lock; it is scoped to the owner's Session (or global outside one), renews for the same owner, and is released on TTL expiry, owner panel exit, or owner Pane archive. `--wait` blocks in the daemon until the lock comes free.\",\n \"`runpane loops list|create|update|run|pause|resume|delete` manage loops (scheduled tasks): a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, agents use their harness's own sleep and wake. See [LOOPS.md](LOOPS.md).\",\n \"`runpane agents start|status|send` finish the three common agent jobs in one call each: start an agent on a task (a new tab in the work's Pane with `--pane <id>`, or a new Pane for new work), check on it, and send it a follow-up.\",\n \"`runpane report --state ready|blocked|failed|done` is how a worker hands back its result. It stores the latest report on the worker's panel (state, `--pr`, `--head`, up to 16,000 characters of `--summary` or `--summary-file`, and the `--question` a blocked worker needs answered), journals an opt-in `agent.report` watch event (`REPORT <pane-name> pane <pane-id> panel <panel-id> ready pr#747 fc5dce9`; it skips the `--min-interval` batch), records it as Session activity, and shows it in `agents status`, `panels list`, and `sessions overview` (each panel's own report as `panes[].panels[].report`, with a JSON object summary as `data`, and the Pane's newest as `panes[].report`). Inside a Pane terminal the panel comes from `PANE_SESSION_ID` and `PANE_PANEL_ID`; elsewhere pass `--pane` and `--panel`. It prints how many watches were waiting for the report and received it (`Delivered to 1 waiting watcher.`, `liveWatchers` in JSON). Zero means nobody was waiting right now, not that the report is lost: it stays on the panel, and a named-cursor watch receives it when it next waits.\",\n \"`runpane notify` asks the person a question, or tells them something, on screen wherever they are, and prints their answer: `--ui options` with 1 to 9 `--choices` returns `{answer, note?}` (the chosen label), or with 2 to 4 `--questions` (`[{title, choices}]`) one pick each, as `{answer, answers}`, `--ui approve` returns `{approved, note?}`, `--ui ask` returns `{answer}`, and `--ui info` returns `{delivered: \\\"screen\\\"|\\\"inbox\\\"}` at once. Any ui takes up to 6 `--links` (`{label, path}`, `{label, url}` or `{label, approval}` for another approval), shown as chips on the card. When the person types a reply it returns `{reply, picks?, id, next}` (or, with no open call, types a `reply:` line into the asking tab): the reply may answer the question or ask one. `runpane notify --took <id|Q-n> answer` records it as the answer; `--took <id|Q-n> question [--status <text>]` shows that the agent is getting more context, and `runpane notify --update <id|Q-n> --asked <text> --what-changed <text>` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` saves a new version and waits on it. `--took` prints `{took, id, next?}`. Only the reply's reader (the asker, the orchestrator that relayed it, or Autopilot) may run them. The call waits with no time limit. A `{waiting: true, id, next}` result means the wait should continue with `runpane notify --resume <id>`; `runpane mcp` asks for it only for clients whose tool calls time out. When Pane is not running or Notify is off it prints `{unavailable, next}` and exits 0, so the agent asks in its terminal. Invalid input fails before anything is shown, with a message naming the field. A Session orchestrator adds `--relays <item>` (an id from `runpane inbox`) to relay a worker's question: the same card gains the choices, context and links, the call waits on it as usual, and the answer also goes straight to the worker's tab. Autopilot's own item takes `--about-session <id|name>`, the Session it is about. It needs no `--yes`: asking the person is the point.\",\n \"Work sent to an agent through `runpane panels create`, `panes create` (with a prompt), or `panels submit` records that the sender waits for a `runpane report`, unless the request says `--source user` (Pane's own UI does) or `--no-expect-report`; `--expect-report` asks for one anyway. Typing into a tab never does. If the agent then goes ready or its panel exits without a report, Pane journals one opt-in `agent.report.missing` entry (`MISSING REPORT <pane-name> pane <pane-id> panel <panel-id> ready|exited`) whose `lastOutput` is the agent's last transcript reply or last screen lines; it skips the `--min-interval` batch and reaches only watchers that list it in `--kinds`. While the agent's screen shows background work (Claude's `· 1 shell` footer or a running background subagent row, Codex's `background terminal running` line), Pane waits and looks again every 10 seconds instead. `agent.blocked` entries carry `screenLines`, the prompt the agent waits on, and are journalled once a panel has stayed blocked for 2 seconds.\",\n \"The orchestrator guide prescribes submit without `--wait`, followed by its named watch; use one completion consumer. As an optional CLI route, `--wait` on `runpane panels submit`, `panels create`, and `panes create` turns the expected report on and blocks on that panel until the first `agent.report`, `agent.report.missing`, `agent.blocked`, `panel.exited`, or `pane.gone`, then prints one line of JSON (`dispatchWaitResult`): the outcome, ids, the report's state, summary, `summaryPath`, PR and head, `screenLines` for a prompt, or `lastOutput` for a missing report, never the transcript. It exits 0 for a report, 3 missing, 4 blocked, 5 exited or gone, and 124 when `--timeout <duration>` passes. It follows the journal from the position the daemon took before delivering the work, so nothing that happens right after delivery is missed. It reuses watch --until-event and returns indeterminate (exit 6) if the journal epoch changes or retained history is lost; inspect the panel and inbox before retrying. Report-expected dispatches are serialized per panel and refused before writing while a result or missing-output collection is outstanding. A send with --no-expect-report is a reply (an answer, or an addition to the current work): it is delivered while a report is outstanding and leaves that expectation in place. Then watch for the original result.\",\n \"`runpane inbox [--session <id|name>] [--json]` lists what a Session's orchestrator has not acknowledged: reports, missing reports, and on-screen questions (a question clears itself once its panel moves on), plus `answers`, what its workers were told, kept 7 days; an answer with no `decision` still needs its D-n. `runpane inbox ack <item-id>...` or `--all --session <id|name>` clears items, and `runpane inbox ack <item-id>... --decision <D-n>` records the D-n, even on an item already cleared. An orchestrator runs it when it starts or resumes, to catch anything that finished while it was down; Pane never injects the inbox into its turns.\",\n \"`runpane panels resume --panel <panel-id>` restarts a stopped terminal panel in place, keeping its panel id: an agent panel relaunches with its resume command (Claude `--resume <session>`, `codex resume <session>`, Agent Farm `agent-farm run <profile> --resume <id>` with the ID Agent Farm printed; the result's `agentSessionId` names it), the same restart the app runs when it shows the panel, without taking focus. A running panel is left alone (`action: \\\"already-running\\\"`); one whose agent exited back to its shell is stopped, and resume runs its saved command again in that shell (`action: \\\"resumed\\\"`). If that command exits at once, `--wait-ready` returns `ok: false` with `reason.code: \\\"command-exited\\\"` and the error line it printed. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope and skips paused ones, `--concurrency` at a time (default 3), then, with `--wait-ready`, waits on the resumed panels together, and returns `{ resumed, alreadyRunning, failed, notReady, items[] }` with each panel's own result, readiness, and blocker or error. After a Pane restart, or once an agent exits back to its shell, stopped panels report `running: false` (`initialized` stays true while the shell lives) in `panels list`, `panels screen`, and `panels wait` (which returns `stopped: true` instead of timing out), and `nextCommand` points at `panels resume`. `runpane watch` reports them as `panel.stopped` (`STOPPED ...`) when `--kinds` lists that kind; other consumers and `workspace state` see `agent.unknown` with `running: false`, never `agent.ready`.\",\n \"`runpane panels pause --panel <panel-id>`, `panes pause --pane <pane-id>` and `sessions pause --session <id|name>` pause a tab, a Pane, or a Session's chat and Panes: Pane stops each terminal tab the way quitting does, without asking, keeps its agent's conversation, and marks it paused. A chat tab can't pause yet: `panels pause --panel` on one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE`, and `panes pause` and `sessions pause` leave its agent running and count it in `chatTabsNotPaused`. A paused tab stays stopped through clicks, app restarts and updates until `panels resume --panel`, `panes resume` or `sessions resume` restarts it in its saved conversation. Each returns `{ action, scope, panelIds, unchanged, failed, chatTabsNotPaused, message, nextCommand }`. `panes list` reports a Pane's `paused` mark (`{ at, by: \\\"manual\\\" | \\\"auto\\\", idleHours? }`) and `inactiveSince`, the latest terminal output or input in its tabs or the last time the person selected it; `panels list` reports each tab's `paused`. `runpane watch` reports `panel.paused` (`PAUSED ...`) and `panel.unpaused` (`UNPAUSED ...`) under `--follow`, to a Session-scoped watch, or when `--kinds` lists them.\",\n \"`runpane panels last-message --panel <panel-id>` reads a Claude or Codex agent's last reply from its transcript (up to `--limit` characters, default 20,000, keeping the end and reporting `truncated`). It never scrapes the screen: without a transcript it prints `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and exits 1.\",\n \"`runpane panels conversation --conversation <id> [--source startup|resume|clear|compact]` records which conversation a terminal panel's agent is on (daemon action `runpane:panels:conversation`, request `{ panelId, paneId?, conversationId, source? }`, result `{ ok: true, paneId, panelId, conversationId, source?, sure }`; `sure` is true when the report came since the panel's agent started). It is not an `agent.report` event. The panel comes from `--panel` (with an optional `--pane`), else `PANE_PANEL_ID` and `PANE_SESSION_ID`, as for `runpane report`. `runpane panels conversation --hook` is the command for a Claude Code `SessionStart` hook: it reads the hook's JSON from stdin (`session_id`, `source`; at most 64 KiB, for at most 2 seconds), reports it for `PANE_PANEL_ID`, prints nothing on stdout or stderr, and always exits 0: outside Pane, on bad input, when Pane is not running, and with a Pane that predates the action. Without `--hook` it prints `Recorded conversation <id> for panel <panel-id>.` and fails like any other command.\",\n \"Commands with a contract `daemonAction` (the `panes` git, script, restore, and move commands, `folders list|create`, and `links open`) call the same Pane daemon channel as the matching button in the app and print `{ ok, data, error }`. Destructive ones add a pane:// `link` to review the Pane.\",\n \"`runpane links create` builds `pane://open?...` links; opening one in Pane selects what it names and never changes Pane state.\",\n \"`runpane docs search|read` search and read Pane docs, help, and installed Pane Chat skills offline. They ship in the npm package and the Pane app only.\",\n \"`runpane handoff` hands a task to another agent on this or another of your machines. The sending agent writes the note from `runpane handoff --template` (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state); `runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md` checks every section is filled in, requires the branch to be pushed (or pushes it with `--push`, never forcing), writes the note to that machine's `~/.pane/handoffs/` and starts the agent in a new Pane branched from the sender's branch. When the sender is a Pane tab running a Claude, Codex, Cursor or OpenCode session and the receiver is the same agent, the handoff carries that session: on another machine its files are copied into that machine's agent store and the receiver resumes the same id; on this machine the receiver resumes a fork, because two agents never resume one session on one machine. The receiver starts with no prompt, and the CLI runs the continuity check and reports JSON `continuity.result` `continued`, `inconclusive` (exit 0; `next` gives a probe question) or `not-continued` (exit 1; the receiver is kept and `next` gives the `--note-only` command). `--note-only`, a different agent, or a tab with no carried session starts a fresh agent with the note (`fresh-agent`). JSON `continuity` is `{ carried, result, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }`: `sessionId` is the sender's session, `resumedId` the id the receiver resumes (the fork's on this machine), `checks` are the continuity checks (`historyLoaded` is null when there was nothing to compare; `originalUntouched` is set for forks), and a dry run sets `carried`, `sessionId`, `fork`, `files` and `bytes` with no `result`. After `continued` or `inconclusive` the sending agent ends its turn and makes no more changes on the branch. An OpenCode session travels as its export: `opencode export` on this machine, then `opencode import` inside the receiving repository there, and an OpenCode receiver is accepted only to continue an OpenCode session. A Cursor session is carried only to another machine, best effort since Cursor's store is undocumented: Cursor has no fork, and a copied chat still writes to the original, so on this machine the result is `not-continued` and nothing is started. A destination's agent store is assumed at its default location (`~/.claude`, `~/.codex`, `~/.cursor`); a copy already there is replaced only when the sender's holds all of it and no agent there runs it, otherwise nothing is copied and the result is `not-continued`. A destination Pane too old to resume a carried session is refused before anything is sent (`ERR_HANDOFF_RESUME_UNSUPPORTED`). From a Session's terminal it skips git and creates a Session with the same name, goal and agent on the other machine instead; a same-agent Session resumes the sender's conversation when first opened. The receiver reports back to the sender's panel.\",\n \"`runpane pr watch`: Register durable GitHub PR monitoring for a Pane.\",\n \"`runpane pr unwatch`: Remove a Pane’s explicit PR registration.\",\n \"`runpane pr list`: List PR registrations and stopped watches.\",\n \"`runpane autopilot status|waiting|stack` read Autopilot (experimental): its switch, run and stack, and its queue: every approval or question waiting on the person on every machine it can reach, grouped by Session, plus `offline` and `cantSee` machines. `runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next]` puts one entry on its stack; relative paths resolve against the caller's directory, and a new entry never replaces the one on screen. `runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) [--note <text>] --because <why>`, `runpane autopilot close <id> --reason <why>` and `runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>]` act on an item, on the machine that holds it, recorded \\\"by Autopilot\\\" on every device; the person can Override an answer and reopen a close. Only Autopilot may run them (`AUTOPILOT_ONLY`), and they refuse a permission prompt with `AUTOPILOT_CANNOT_ANSWER`. An item someone else already answered is refused with who did.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes ...`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. `agent-context` and `version` accept and ignore `--pane-dir`, so one `--pane-dir` can be passed to every runpane command. In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, use Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane`), which reaches the Windows named-pipe daemon through WSL interop; older Pane builds need `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'`.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers emit best-effort telemetry to `https://runpane.com/api/runpane/telemetry` for command failures, download request/success/failure, and GitHub fallback usage. Routine command starts and successes emit nothing. A command failure is a thrown error, or a non-zero exit from `install` or `update` (also when setup runs them); other exit codes are documented results (such as `panels wait` not ready or `doctor` findings) and emit nothing. Polling and status commands (`panes list`, `panels wait`, `watch`, `panes archive`, `workspace state`, `agents status`, `doctor`) emit no failure events, and neither does a `not_found` failure outside `install` and `update`. A failure event carries the command, `failure_stage`, `failure_category`, `exit_code` when known, and `error_code` (the code printed in the `--json` error). Both wrappers share a per-install limit in `<PANE_DIR>/runpane-wrapper-telemetry.json`: one failure event per cause per hour and at most 10 per hour.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so installs reporting failures or downloads can be counted with `count(DISTINCT properties.install_id)`; this is not a count of all CLI users.\",\n \"Wrapper telemetry must be disabled in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `TF_BUILD`, `JENKINS_URL`, `TEAMCITY_VERSION` or `CODEBUILD_BUILD_ID` set), when `DO_NOT_TRACK=1` or `RUNPANE_TELEMETRY_DISABLED` is set, and when the local config explicitly disables analytics. It must not include raw paths, labels, prompts, raw error text, or environment values.\",\n \"Desktop local control reports useful failures as `runpane_local_control_failed`; normal polling, wait deadlines, and optional agent unavailability emit nothing. Reports contain coarse operation and failure classification only, with a five-minute deduplication interval and a maximum of 20 per hour per app process. Handled CLI failures are ordinary events, not exceptions.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"doctor\",\n \"--report\",\n \"--title\",\n \"watch failed\",\n \"--body-file\",\n \"/tmp/watch-evidence.txt\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"monitor\",\n \"--from\",\n \"earliest\",\n \"--follow\",\n \"--heartbeat\",\n \"60s\",\n \"--idle-after\",\n \"600000\",\n \"--ready-settle\",\n \"180000\",\n \"--blocked-settle\",\n \"30000\",\n \"--min-interval\",\n \"600000\",\n \"--expire-after\",\n \"10800000\",\n \"--exclude-pane\",\n \"pane-old\",\n \"--kinds\",\n \"agent.ready,agent.idle\",\n \"--include-held-input\",\n \"--self-test\"\n ],\n [\n \"daemon\",\n \"repair\",\n \"--pane-dir\",\n \"/tmp/pane-remote\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"cost\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--pinned\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"pin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"unpin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"rename\",\n \"--pane\",\n \"session-1\",\n \"--name\",\n \"issue-393\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"focus\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"user\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"list\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"get\",\n \"--session\",\n \"demo\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"update\",\n \"--session\",\n \"demo\",\n \"--from-json\",\n \"-\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel=panel-1\",\n \"--text=- [ ] item\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"commit\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"fix: title\\n\\nbody\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"links\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--panel\",\n \"panel-1\",\n \"--json\"\n ],\n [\n \"links\",\n \"open\",\n \"--url=pane://open?pane=pane-1\",\n \"--yes\"\n ],\n [\n \"mcp\",\n \"--toolsets\",\n \"core,git\",\n \"--read-only\"\n ],\n [\n \"docs\",\n \"search\",\n \"--query\",\n \"archive a pane\",\n \"--limit\",\n \"3\"\n ],\n [\n \"docs\",\n \"read\",\n \"--doc\",\n \"docs/PANE_MCP.md\"\n ],\n [\n \"agents\",\n \"start\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"fix-login\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Fix the login redirect\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agents\",\n \"send\",\n \"--pane\",\n \"pane-1\",\n \"--text=- [ ] item\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--keys\",\n \"down,enter\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"squash-rebase\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"feat: land it\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"folders\",\n \"create\",\n \"--repo\",\n \"3\",\n \"--name\",\n \"Reviews\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"move\",\n \"--pane\",\n \"pane-1\",\n \"--folder\",\n \"folder-9\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"watch\",\n \"--follow\"\n ],\n [\n \"watch\",\n \"--session\",\n \"__orchestration_session_1b4e28ba-2fa1-11d2-883f-0016d3cca427__\",\n \"--follow\",\n \"--json\",\n \"--kinds\",\n \"agent.ready,agent.blocked,pane.associated,pane.detached,pr.conflicted,pr.checks,pr.merged\",\n \"--ready-settle\",\n \"180000\"\n ],\n [\n \"watch\",\n \"--session=Release train\",\n \"--as\",\n \"release-watch\",\n \"--exclude-pane\",\n \"pane-old\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\",\n \"--pane-dir=/tmp/pane\"\n ],\n [\n \"version\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--agent\",\n \"claude\",\n \"--base-branch\",\n \"origin/release\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--tool-command\",\n \"wrapper codex-run\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"farm\",\n \"--tool-command\",\n \"agent-farm run free-range\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Plan\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"long\",\n \"--agent\",\n \"claude\",\n \"--initial-input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"ready\",\n \"--pr\",\n \"747\",\n \"--head\",\n \"FC5DCE9\",\n \"--summary\",\n \"Done: tests pass\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"blocked\",\n \"--question\",\n \"Which API version?\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--summary-file\",\n \"-\"\n ],\n [\n \"report\",\n \"--state=done\",\n \"--summary-file=/tmp/report.md\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--all-stopped\",\n \"--wait-ready\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"last-message\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"5000\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation\",\n \"0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44\",\n \"--source\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation=abc\",\n \"--source=compact\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--hook\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--base\",\n \"release/foo\",\n \"--branch\",\n \"agents/w5a\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"prompt.md\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5b\",\n \"--base-branch\",\n \"origin/main\",\n \"--branch\",\n \"agents/w5b\",\n \"--worktree-name\",\n \"w5b-dir\",\n \"--tool-command\",\n \"bash\",\n \"--initial-input-file\",\n \"-\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--remove-worktree\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--remove-worktree\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"testing-account\",\n \"--ttl\",\n \"30m\",\n \"--wait\",\n \"1800000\",\n \"--note\",\n \"call QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"staging-db\",\n \"--ttl\",\n \"1500\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name=db\",\n \"--ttl=2h\"\n ],\n [\n \"lock\",\n \"release\",\n \"--name\",\n \"testing-account\",\n \"--force\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"list\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"--help\"\n ],\n [\n \"panels\",\n \"open\",\n \"--pane\",\n \"session-1\",\n \"--file\",\n \"plan.html\",\n \"--title\",\n \"Plan\",\n \"--split\",\n \"--no-focus\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"open\",\n \"--url\",\n \"http://localhost:3000\",\n \"--tab\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"correction\",\n \"--interrupt\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"repos\",\n \"get\",\n \"--repo\",\n \"Pane\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--build-script\",\n \"pnpm install\",\n \"--run-script\",\n \"pnpm dev\",\n \"--worktree-folder\",\n \"../wt\",\n \"--clear\",\n \"archive-script,system-prompt\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"orch\",\n \"--until-event\",\n \"--json\",\n \"--kinds\",\n \"agent.report,agent.blocked,panel.exited,pane.gone\",\n \"--timeout-ms\",\n \"600000\"\n ],\n [\n \"pr\",\n \"watch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"unwatch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"list\",\n \"--pane\",\n \"worker\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"0\"\n ],\n [\n \"watch\",\n \"--until-event\",\n \"--timeout-ms\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--timeout-ms\",\n \"off\"\n ],\n [\n \"watch\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"off\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--blocked-settle\",\n \"2s\",\n \"--min-interval\",\n \"1m\",\n \"--idle-after\",\n \"20m\",\n \"--expire-after\",\n \"off\",\n \"--heartbeat\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"go\",\n \"--expect-report\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"claude\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"codex\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"inbox\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"in-5e6f7a8b\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"--all\",\n \"--session\",\n \"Release\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"fyi\",\n \"--no-expect-report\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"prompt.md\",\n \"--as-file-pointer\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Red\",\n \"--wait\",\n \"--timeout\",\n \"30m\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"task.md\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"deploy\",\n \"--wait\",\n \"5000\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"nightly\",\n \"--cron\",\n \"0 3 * * *\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Sweep bugs\",\n \"--worktree-name\",\n \"nightly-sweep\",\n \"--paused\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo=2\",\n \"--name=deps\",\n \"--cron=0 9 * * 1\",\n \"--tool-command\",\n \"./scripts/nightly-report.sh\",\n \"--prompt-file\",\n \"-\"\n ],\n [\n \"loops\",\n \"create\",\n \"--session\",\n \"new\",\n \"--name\",\n \"triage\",\n \"--cron\",\n \"0 9 * * 1-5\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Triage\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--cron\",\n \"0 4 * * *\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--session\",\n \"Nightly\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"run\",\n \"--loop=loop-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"pause\",\n \"--loop\",\n \"loop-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"loops\",\n \"list\",\n \"--json\"\n ],\n [\n \"loops\",\n \"--help\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Ship it?\",\n \"--context\",\n \"Tests pass.\",\n \"--choices=[{\\\"label\\\":\\\"Yes\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"No\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--ui=approve\",\n \"--title=Drop the table?\",\n \"--detail=DROP TABLE t;\",\n \"--detail-label=Show the SQL\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which branch?\",\n \"--suggestions=[\\\"main\\\"]\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"info\",\n \"--title\",\n \"Done\",\n \"--action={\\\"label\\\":\\\"Open\\\",\\\"url\\\":\\\"https://example.com\\\"}\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"approve\",\n \"--title\",\n \"Ship the notes?\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR\\\",\\\"url\\\":\\\"https://example.com/pr/1\\\"}]\"\n ],\n [\n \"notify\",\n \"--resume\",\n \"n-1\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"list\",\n \"--project\",\n \"stripe\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"set\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\"\n ],\n [\n \"secrets\",\n \"delete\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"stripe\",\n \"--only\",\n \"A,B\",\n \"--env\",\n \"KEY=STRIPE_SECRET_KEY\",\n \"--env=TOKEN=GH_TOKEN\",\n \"--file\",\n \"CERT\",\n \"--\",\n \"node\",\n \"-e\",\n \"run()\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"qa\",\n \"sh\",\n \"-c\",\n \"echo ok\"\n ],\n [\n \"secrets\",\n \"request\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--title\",\n \"Stripe test key\",\n \"--reason\",\n \"- for the webhook tests\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--env\",\n \"[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"x\\\"}]\",\n \"--env-remove\",\n \"OLD,STALE\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"session-a\",\n \"--merged\",\n \"--closed\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"status\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"waiting\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--item\",\n \"n-1\",\n \"--note\",\n \"Needs a go\",\n \"--next\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--file\",\n \"/tmp/plan.html\",\n \"--title\",\n \"Plan\",\n \"--tab\",\n \"https://localhost:5173\",\n \"--tab=/tmp/notes.md\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--session\",\n \"Release\"\n ],\n [\n \"watch\",\n \"--as\",\n \"autopilot\",\n \"--kinds\",\n \"item.created,item.answered,session.created,session.archived,orchestrator.turn_ended\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"--decision\",\n \"D-147\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--relays\",\n \"in-1a2b3c4d\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Q-39: Run it?\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which?\",\n \"--about-session=Release\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--choice\",\n \"Yes\",\n \"--note\",\n \"Go\",\n \"--because\",\n \"D-22\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--approve\",\n \"--because=The person said so\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--deny\",\n \"--because\",\n \"D-23\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--text\",\n \"main\",\n \"--because\",\n \"D-24\"\n ],\n [\n \"autopilot\",\n \"close\",\n \"n-1\",\n \"--reason\",\n \"The PR merged\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"rewrite\",\n \"n-1\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Q-3: Which?\",\n \"--context\",\n \"Why\",\n \"--choices=[{\\\"label\\\":\\\"A\\\"}]\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"url\\\":\\\"https://example.com\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"answer\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took=n-1\",\n \"question\",\n \"--status\",\n \"Checking the logs\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"--took-as\",\n \"answer\"\n ],\n [\n \"notify\",\n \"--update\",\n \"Q-20\",\n \"--asked\",\n \"Can it be undone?\",\n \"--what-changed\",\n \"- Added: it can be undone\",\n \"--questions\",\n \"[{\\\"title\\\":\\\"A\\\",\\\"choices\\\":[{\\\"label\\\":\\\"x\\\"}]},{\\\"title\\\":\\\"B\\\",\\\"choices\\\":[{\\\"label\\\":\\\"y\\\"}]}]\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane repos rename\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\",\n \"runpane loops list\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\",\n \"python -m runpane loops list\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"description\": \"A failed command under --json, printed on stdout with a non-zero exit. The same message, plus a Next line, goes to stderr.\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\",\n \"description\": \"What happened, why, and what was or was not changed.\"\n },\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable identifier to branch on, such as ENOENT or ERR_UNKNOWN_CHANNEL. ERR_RUNPANE_FAILED when the failure has no specific code.\"\n },\n \"next\": {\n \"type\": \"string\",\n \"description\": \"The step to take next, when one is known.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteDaemonService\",\n \"remoteSetup\",\n \"watchDefaults\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteDaemonService\": {\n \"type\": \"object\",\n \"required\": [\n \"paneDir\",\n \"managed\",\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"managed\": {\n \"type\": \"boolean\"\n },\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"executableHealth\": {\n \"type\": \"object\",\n \"required\": [\n \"processImage\",\n \"restart\",\n \"checkedAt\"\n ],\n \"properties\": {\n \"processImage\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"runtimePath\",\n \"installedPath\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"current\",\n \"replaced\",\n \"deleted\",\n \"unknown\"\n ]\n },\n \"runtimePath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"installedPath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"restart\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"ready\",\n \"broken\",\n \"unknown\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"resolvedPath\": {\n \"type\": \"string\"\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"diagnosticCode\": {\n \"enum\": [\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n },\n \"checkedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\",\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"watchDefaults\": {\n \"type\": \"object\",\n \"required\": [\n \"follow\",\n \"untilEvent\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"follow\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"summary\",\n \"kinds\",\n \"readySettle\",\n \"blockedSettle\",\n \"minInterval\",\n \"idleAfter\",\n \"idleBackoff\",\n \"expireAfter\",\n \"heartbeat\",\n \"includeShells\",\n \"heldInputDetection\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"readySettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"blockedSettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"minInterval\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"expireAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"heartbeat\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"includeShells\": {\n \"type\": \"boolean\"\n },\n \"heldInputDetection\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"untilEvent\": {\n \"type\": \"object\",\n \"required\": [\n \"summary\",\n \"kinds\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n },\n \"cliInstalls\": {\n \"type\": \"object\",\n \"required\": [\n \"installs\",\n \"warnings\"\n ],\n \"properties\": {\n \"managedVersion\": {\n \"type\": \"string\"\n },\n \"installs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"origin\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"origin\": {\n \"enum\": [\n \"pane\",\n \"npm\",\n \"other\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"npmPrefix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"path\",\n \"message\",\n \"fix\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"RUNPANE_STALE_INSTALL\",\n \"RUNPANE_STALE_UNMANAGED\",\n \"RUNPANE_STALE_MANAGED\",\n \"RUNPANE_MANAGED_BEHIND\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"fixed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"ok\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"workspacesSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"machine\",\n \"otherMachines\",\n \"lines\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"This machine's Tailscale name.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"description\": \"Why workspaces are off.\"\n },\n \"fix\": {\n \"type\": \"string\",\n \"description\": \"The one step that turns them on.\"\n },\n \"otherMachines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"os\",\n \"online\",\n \"owner\",\n \"mine\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own Tailscale login.\"\n }\n }\n }\n },\n \"lines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The block printed by help, doctor, and agent-context.\"\n }\n }\n },\n \"daemonRepairResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"paneDir\",\n \"strategy\",\n \"launcherPath\",\n \"before\",\n \"after\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"systemd-user\",\n \"launch-agent\",\n \"scheduled-task\",\n \"manual\",\n \"skipped\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"before\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"after\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"scriptSource\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"source\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"scriptSetting\": {\n \"type\": \"object\",\n \"required\": [\n \"value\",\n \"fallback\",\n \"effective\"\n ],\n \"properties\": {\n \"value\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"fallback\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"effective\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"settings\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"currentBranch\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"configFile\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"envVars\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"currentBranch\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"configFile\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"buildScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"runScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"archiveScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"envVars\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"repoUpdateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n },\n \"settings\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"buildScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"runScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"archiveScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"envVars\": {\n \"type\": \"object\",\n \"description\": \"set adds or replaces variables by key; remove deletes them by key.\",\n \"minProperties\": 1,\n \"properties\": {\n \"set\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n },\n \"remove\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoUpdateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"changed\": {\n \"type\": \"array\",\n \"items\": {\n \"enum\": [\n \"name\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"envVars\"\n ]\n }\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"name\"\n ],\n \"properties\": {\n \"repo\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"updatedAt\",\n \"machine\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"type\": \"string\",\n \"description\": \"ISO time it was last set.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine where it was last set.\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"secrets\",\n \"storage\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"secrets\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"storage\": {\n \"type\": \"object\",\n \"description\": \"Where this machine keeps values. encrypted is false only for the file store on a machine without an OS keyring.\",\n \"required\": [\n \"kind\",\n \"encrypted\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"keychain\",\n \"file\"\n ]\n },\n \"encrypted\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetRequest\": {\n \"description\": \"What `runpane secrets set` sends on runpane:secrets:set. value comes from stdin.\",\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"value\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"value\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"replaced\",\n \"secret\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"replaced\": {\n \"type\": \"boolean\"\n },\n \"secret\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"projectEnvVar\": {\n \"description\": \"A project environment variable. Plain values are shown in full; keychain and Pane secret variables are references, never values.\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"key\",\n \"source\",\n \"value\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"plain\"\n },\n \"value\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"An existing macOS keychain generic-password item.\",\n \"required\": [\n \"key\",\n \"source\",\n \"service\",\n \"account\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"keychain\"\n },\n \"service\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"account\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The Pane secret project/name.\",\n \"required\": [\n \"key\",\n \"source\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"secret\"\n },\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"branch\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\",\n \"cursor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"associateSession\": {\n \"type\": \"string\",\n \"description\": \"Session id to associate each created Pane with; the CLI sets it from PANE_ORCHESTRATION_SESSION_ID unless --no-associate.\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"staged\": {\n \"type\": \"boolean\"\n },\n \"attempts\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"association\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"ok\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"setupQueue\": {\n \"type\": \"object\",\n \"required\": [\n \"position\",\n \"message\"\n ],\n \"properties\": {\n \"position\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"required\": [\n \"ref\",\n \"refresh\"\n ],\n \"properties\": {\n \"ref\": {\n \"type\": \"string\"\n },\n \"commit\": {\n \"type\": \"string\"\n },\n \"refresh\": {\n \"type\": \"string\",\n \"enum\": [\n \"fetched\",\n \"recent\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"notice\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\",\n \"pinned\",\n \"agentStatus\",\n \"ownership\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"inactiveSince\": {\n \"type\": \"string\",\n \"description\": \"When the Pane was last used: the latest terminal output or input in its tabs, or the last time the person selected it (ISO 8601).\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\",\n \"description\": \"Set while every terminal tab of the Pane is paused: the latest tab's mark.\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"agentStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"agentState\": {\n \"enum\": [\n \"ready\",\n \"working\",\n \"blocked\",\n \"none\"\n ]\n },\n \"ownership\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostResult\": {\n \"$defs\": {\n \"usageTotals\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"usageByModel\": {\n \"type\": \"object\",\n \"required\": [\n \"model\",\n \"provider\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"model\": {\n \"type\": \"string\"\n },\n \"provider\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostSlice\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"pane\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"paneName\",\n \"worktreePath\",\n \"repoId\",\n \"archived\",\n \"createdAtMs\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"fromMs\",\n \"toMs\",\n \"pricingAsOf\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"fromMs\": {\n \"type\": \"number\"\n },\n \"toMs\": {\n \"type\": \"number\"\n },\n \"pricingAsOf\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/pane\"\n }\n },\n \"unattributed\": {\n \"$ref\": \"#/$defs/paneCostSlice\"\n },\n \"totals\": {\n \"$ref\": \"#/$defs/usageTotals\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"gen\",\n \"at\",\n \"kind\",\n \"paneId\",\n \"paneName\",\n \"source\"\n ],\n \"properties\": {\n \"gen\": {\n \"type\": \"number\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"agent.ready\",\n \"agent.busy\",\n \"agent.blocked\",\n \"agent.unknown\",\n \"agent.idle\",\n \"pane.created\",\n \"pane.gone\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"agent.report\",\n \"agent.report.missing\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\",\n \"item.created\",\n \"item.answered\",\n \"session.created\",\n \"session.archived\",\n \"orchestrator.turn_ended\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"to\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"source\": {\n \"enum\": [\n \"agent\",\n \"exit\",\n \"session\",\n \"github\"\n ]\n },\n \"reason\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"settledMs\": {\n \"type\": \"number\"\n },\n \"idleMs\": {\n \"type\": \"number\"\n },\n \"idleCount\": {\n \"type\": \"number\"\n },\n \"heldInput\": {\n \"type\": \"string\"\n },\n \"heldInputPresent\": {\n \"type\": \"boolean\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Baseline agent entries: whether the panel's terminal process is live. A stopped panel is never agent.ready.\"\n },\n \"baseline\": {\n \"const\": true\n },\n \"replay\": {\n \"const\": true,\n \"description\": \"Set on baseline entries delivered after a reset. A replayed entry restates current state and is never a new transition: a replayed agent.ready is not READY.\"\n },\n \"changedWhileAway\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Named Session of a pane.associated or pane.detached entry.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"description\": \"PR of a pr.conflicted, pr.checks, or pr.merged entry.\",\n \"required\": [\n \"number\",\n \"url\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"description\": \"Base branch of a pr.base_changed (old to new) or pr.base_deleted (to is the PR's current base name) entry.\",\n \"required\": [\n \"from\",\n \"to\"\n ],\n \"properties\": {\n \"from\": {\n \"type\": \"string\"\n },\n \"to\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"checks\": {\n \"enum\": [\n \"passed\",\n \"failed\"\n ],\n \"description\": \"Failures are immediate; success requires complete nonempty required checks on the pinned head.\"\n },\n \"failingChecks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Newly failing check names on this head.\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\",\n \"description\": \"Report of an agent.report entry; its summary is cut to 2,000 characters (the panel keeps up to 16,000).\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Added to watch --json NDJSON events. Persist with gen after processing for numeric rearm.\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentState\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"running\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"lastOutput\": {\n \"type\": \"string\",\n \"description\": \"What an agent.report.missing entry's agent said last, up to 2,000 characters (the end is kept).\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ],\n \"description\": \"transcript: the agent's last reply; screen: the last lines on its screen.\"\n },\n \"expectedSince\": {\n \"type\": \"string\",\n \"description\": \"When the work an agent.report.missing entry is about was sent.\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The screen lines of the prompt an agent.blocked entry's agent waits on (at most 20).\"\n },\n \"itemId\": {\n \"type\": \"string\",\n \"description\": \"The Notify item of an item.created or item.answered entry; `runpane autopilot stack push --item <id>` takes it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentReport\": {\n \"type\": \"object\",\n \"description\": \"The latest runpane report from an agent panel.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelReport\": {\n \"type\": \"object\",\n \"description\": \"A panel's latest runpane report in sessions overview. data is the summary when it is a complete JSON object, returned as the worker gave it; a plain-text, array, scalar, invalid, more than 64 levels deep, or truncated summary has no data.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n },\n \"data\": {\n \"type\": \"object\",\n \"description\": \"The worker's JSON object summary, returned as given. Pane never reads, renames or validates its fields.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"since\": {\n \"type\": \"number\"\n },\n \"as\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"now\",\n \"earliest\"\n ]\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry/properties/kind\"\n }\n },\n \"paneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Named Session id or exact name. Limits the wait to the Session's associated Panes, resolved on every read, and implies the opt-in pane.associated, pane.detached, and pr.* entries. Cannot be combined with paneIds.\"\n },\n \"excludePaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"nameContains\": {\n \"type\": \"string\"\n },\n \"agentsOnly\": {\n \"type\": \"boolean\"\n },\n \"ackNow\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInput\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInputPresence\": {\n \"type\": \"boolean\"\n },\n \"idleAfterMs\": {\n \"type\": \"number\"\n },\n \"idleWindowStartMs\": {\n \"type\": \"number\"\n },\n \"settleMs\": {\n \"type\": \"number\"\n },\n \"blockedSettleMs\": {\n \"type\": \"number\"\n },\n \"minIntervalMs\": {\n \"type\": \"number\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Expected epoch for since. Requires since and cannot be combined with as. Mismatch returns epoch-changed reset.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceStateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\",\n \"timedOut\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"dropped\": {\n \"type\": \"number\"\n },\n \"reset\": {\n \"type\": \"object\",\n \"required\": [\n \"reason\"\n ],\n \"properties\": {\n \"reason\": {\n \"enum\": [\n \"first-use\",\n \"epoch-changed\",\n \"cursor-truncated\",\n \"unknown-consumer\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"session\": {\n \"type\": \"object\",\n \"description\": \"The Session a session request resolved to. A client that sent session and gets no session back is talking to a daemon that ignored it.\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"activityGeneration\": {\n \"type\": \"number\",\n \"description\": \"Latest meaningful activity in scope, including events hidden by output kinds or cadence. Natural expiry resets when it advances.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"favoritePinnedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"pane\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"pane\": {\n \"$ref\": \"#/jsonSchemas/paneListResult/properties/panes/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"focused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"focused\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ],\n \"description\": \"`completed`: the worktree is gone from its path and from git (see trashDeletion). `failed`: removal failed and the worktree may remain; ok is false. `timeout`: the archive script or git removal is still running in the background; ok stays true. `not-applicable`: nothing was removed (main-repo Pane, or an adopted worktree without --remove-worktree). Released CLIs decode exactly these values.\"\n },\n \"trashDeletion\": {\n \"enum\": [\n \"pending\",\n \"done\"\n ],\n \"description\": \"Present with worktreeCleanup `completed`. `pending`: the removed worktree was moved into the repository's .git/pane-trash and its files are still being deleted in the background. `done`: the files are gone.\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"upstreamGone\": {\n \"type\": \"boolean\",\n \"description\": \"The branch had an upstream that no longer exists on the remote, so commits were compared with the base branch.\"\n },\n \"mergedViaPr\": {\n \"type\": \"object\",\n \"description\": \"A merged pull request whose head commit is HEAD. Its commits are not counted as unpushed.\",\n \"required\": [\n \"number\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"busyAgents\": {\n \"type\": \"array\",\n \"description\": \"Agents in the Pane that are mid-turn (working) or waiting for an answer (blocked). Archiving would stop them, so archive refuses with agent-busy unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"state\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"enum\": [\n \"working\",\n \"blocked\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"unsentInput\": {\n \"type\": \"array\",\n \"description\": \"Tabs holding input a person typed that archiving would lose: an agent composer's unsent text or an editor's unsaved edits. Archive refuses with unsent-input unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"kind\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"composer\",\n \"editor\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"dryRun\",\n \"wouldArchive\",\n \"forced\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"wouldArchive\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/2/properties/blocked\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"busyAgents\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/busyAgents\"\n },\n \"unsentInput\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/unsentInput\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"dock\": {\n \"type\": \"boolean\",\n \"description\": \"True on the Pane's terminal dock (its plain shell); omitted on every other panel.\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"resumeId\": {\n \"type\": \"string\"\n },\n \"resumeSource\": {\n \"enum\": [\n \"agent-farm\",\n \"configured\",\n \"agent\"\n ]\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n },\n \"openedBy\": {\n \"type\": \"object\",\n \"description\": \"Who opened this tab through runpane, and the tab that opened it when known. Omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose source is agent.\",\n \"required\": [\n \"source\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"agent\",\n \"user\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\",\n \"composer\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"composer\": {\n \"type\": \"object\",\n \"required\": [\n \"isPresent\",\n \"hasUndeliveredText\"\n ],\n \"properties\": {\n \"isPresent\": {\n \"type\": \"boolean\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"ghostText\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"asFilePointer\": {\n \"type\": \"boolean\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"interrupt\": {\n \"type\": \"boolean\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n },\n \"operationId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"enum\": [\n \"cr\",\n \"tab\",\n \"none\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"acp\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"evidence\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"taken\",\n \"queued\",\n \"in-composer\",\n \"unknown\"\n ]\n },\n \"evidence\": {\n \"enum\": [\n \"transcript\",\n \"screen\",\n \"argv\",\n \"acp-receipt\"\n ]\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human explanation of delivery; queued means the agent has not read the message yet.\"\n }\n },\n \"additionalProperties\": false\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitComposerResult/properties/blocked\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"leading-bang-runs-shell\",\n \"leading-hash-memory\",\n \"leading-slash-command\",\n \"leading-at-mention\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n },\n \"operationId\": {\n \"type\": \"string\",\n \"description\": \"Durable host operation ID for Chat delivery reconciliation.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"stopped\": {\n \"const\": true,\n \"description\": \"The wait did not match and the panel's terminal is not running (ready and idle waits return this after a 2-second grace instead of timing out); nextCommand is `runpane panels resume`.\"\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"action\",\n \"message\",\n \"panel\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ],\n \"description\": \"resumed: the panel's terminal was restarted in place. already-running: nothing was restarted.\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\",\n \"description\": \"The agent conversation the panel resumes, when Pane knows it.\"\n },\n \"panel\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeManyResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"scope\",\n \"resumed\",\n \"alreadyRunning\",\n \"failed\",\n \"notReady\",\n \"concurrency\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"Every stopped panel in scope resumed and, with --wait-ready, became ready.\"\n },\n \"scope\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\",\n \"sessionName\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"all-stopped\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"resumed\": {\n \"type\": \"number\"\n },\n \"alreadyRunning\": {\n \"type\": \"number\",\n \"description\": \"Agent panels in scope that were already running and were left alone.\"\n },\n \"failed\": {\n \"type\": \"number\",\n \"description\": \"Panels that could not be restarted; their item carries error.\"\n },\n \"notReady\": {\n \"type\": \"number\",\n \"description\": \"With --wait-ready: resumed panels that were blocked or not ready within --ready-timeout-ms.\"\n },\n \"concurrency\": {\n \"type\": \"number\",\n \"description\": \"Panels restarted at once.\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"paneName\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why this panel could not be resumed; the others still were.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelLastMessageResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"agentType\",\n \"text\",\n \"length\",\n \"limit\",\n \"truncated\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"length\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"truncated\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"reason\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"reason\": {\n \"const\": \"transcript-unavailable\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelConversationRequest\": {\n \"type\": \"object\",\n \"description\": \"What runpane panels conversation sends the daemon (runpane:panels:conversation).\",\n \"required\": [\n \"panelId\",\n \"conversationId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"conversationId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 256\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelConversationResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"conversationId\",\n \"sure\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"conversationId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n },\n \"sure\": {\n \"type\": \"boolean\",\n \"description\": \"True when this report came since the panel's agent started, so Pane knows the panel is on this conversation.\"\n },\n \"generation\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"type\",\n \"title\",\n \"placement\",\n \"active\",\n \"reused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"enum\": [\n \"browser\",\n \"editor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"reused\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\",\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n \"force\": {\n \"type\": \"boolean\",\n \"description\": \"Close it as the tab's close button does, skipping every check but permanent: for a stuck tab, or one the person asked to close. A busy agent's turn and unsent input are lost.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"closed\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when the close was refused (the tab stays open); the CLI exits 1.\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\",\n \"description\": \"Omitted only when the tab was already gone.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"The tab's type, such as terminal, chat, browser or editor. Omitted only when the tab was already gone.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"closed\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\",\n \"description\": \"Closed with --force, skipping the checks that would have kept it.\"\n },\n \"alreadyClosed\": {\n \"type\": \"boolean\",\n \"description\": \"No tab has this id: it was closed already.\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"wouldClose\": {\n \"type\": \"boolean\",\n \"description\": \"Dry run only: whether the close would go ahead.\"\n },\n \"refused\": {\n \"type\": \"object\",\n \"description\": \"Why the tab was kept open. opened-by-user: not opened by an agent with --source agent (including tabs from before Pane recorded openers). permanent: a permanent tab. agent-busy: its agent is mid-turn or waiting for an answer. unsent-input: unsent composer text or unsaved editor edits. input-unknown: a live tab whose input Pane cannot read.\",\n \"required\": [\n \"code\",\n \"message\",\n \"next\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"opened-by-user\",\n \"permanent\",\n \"agent-busy\",\n \"unsent-input\",\n \"input-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"composer-unknown\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"sessionListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessions\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"selectedSessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"internalSessionId\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"object\",\n \"description\": \"Only when the update archived the Session: what happened to each associated Pane.\",\n \"required\": [\n \"archivedPaneIds\",\n \"keptPanes\",\n \"failedPanes\"\n ],\n \"properties\": {\n \"archivedPaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"keptPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"code\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"failedPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"error\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"sessionOverviewResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"status\",\n \"panes\",\n \"activity\",\n \"refreshedAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/panelReport\"\n }\n }\n }\n }\n }\n }\n },\n \"activity\": {\n \"type\": \"array\"\n },\n \"refreshedAt\": {\n \"type\": \"string\"\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"locks\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"scope\",\n \"owner\",\n \"acquiredAt\",\n \"expiresAt\",\n \"ttlMs\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"scope\": {\n \"enum\": [\n \"session\",\n \"global\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"acquiredAt\": {\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"ttlMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ttlMs\",\n \"owner\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"ttlMs\": {\n \"type\": \"number\"\n },\n \"waitMs\": {\n \"type\": \"number\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"renewed\",\n \"waitedMs\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acquired\": {\n \"const\": true\n },\n \"renewed\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"timedOut\",\n \"waitedMs\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"acquired\": {\n \"const\": false\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"lockReleaseResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"forced\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"released\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"reason\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"released\": {\n \"const\": false\n },\n \"reason\": {\n \"const\": \"not-owner\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"daemonActionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"data\": {},\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"link\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"linkCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"url\",\n \"target\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"target\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"repo\",\n \"session\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"docsSearchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"query\",\n \"results\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"query\": {\n \"type\": \"string\"\n },\n \"results\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"title\",\n \"kind\",\n \"excerpt\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"excerpt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"docsReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"path\",\n \"title\",\n \"kind\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentTaskResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"link\",\n \"ready\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"promptDelivered\": {\n \"type\": \"boolean\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"status\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"working\",\n \"ready\",\n \"blocked\",\n \"idle\",\n \"exited\",\n \"unknown\"\n ]\n },\n \"screen\": {\n \"type\": \"string\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentSendResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"delivered\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"blocked\": {\n \"type\": \"string\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextUnknownCommandError\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"code\",\n \"message\",\n \"candidates\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"code\": {\n \"const\": \"unknown_command\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"candidates\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"reportResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"report\",\n \"sessionIds\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"sessionIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"liveWatchers\": {\n \"type\": \"number\",\n \"description\": \"Watches that were waiting for this report when it was journaled and received it. A snapshot: a named-cursor watch that waits later still receives it. Absent from older daemons.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"merged\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"type\": \"boolean\",\n \"description\": \"Also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessionId\",\n \"merged\",\n \"removeWorktree\",\n \"archived\",\n \"skipped\",\n \"failed\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n },\n \"archived\": {\n \"type\": \"number\",\n \"description\": \"Panes archived, or that would be archived with --dry-run.\"\n },\n \"skipped\": {\n \"type\": \"number\"\n },\n \"failed\": {\n \"type\": \"number\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"outcome\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"outcome\": {\n \"enum\": [\n \"archived\",\n \"would-archive\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"skipped\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\",\n \"missing-pane\",\n \"already-archived\",\n \"main-repo\",\n \"pr-open\",\n \"pr-not-merged\",\n \"pr-status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"open\",\n \"merged\",\n \"closed\",\n \"none\",\n \"unknown\"\n ]\n },\n \"number\": {\n \"type\": \"number\"\n },\n \"reason\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"worktreeCleanup\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/worktreeCleanup\"\n },\n \"trashDeletion\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/trashDeletion\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"prWatchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"watch\": {\n \"type\": \"object\"\n },\n \"watches\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"removed\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"inboxListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"sessionId\",\n \"kind\",\n \"at\",\n \"paneId\",\n \"paneName\",\n \"panelId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"report\",\n \"report.missing\",\n \"blocked\"\n ]\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"answers\": {\n \"type\": \"array\",\n \"description\": \"The answers the Session's workers got, kept 7 days. Absent from an older Pane.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"answer\",\n \"by\",\n \"worker\",\n \"answeredAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"answer\": {\n \"type\": \"string\"\n },\n \"by\": {\n \"enum\": [\n \"you\",\n \"autopilot\",\n \"orchestrator\",\n \"pane\"\n ],\n \"description\": \"Who answered.\"\n },\n \"because\": {\n \"type\": \"string\"\n },\n \"worker\": {\n \"type\": \"string\",\n \"description\": \"The worker that asked.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"answeredAt\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"enum\": [\n \"call\",\n \"tab\"\n ],\n \"description\": \"How it reached the worker; absent while its tab is busy.\"\n },\n \"undelivered\": {\n \"type\": \"string\",\n \"description\": \"Why Pane can't type it into the worker's tab (its tab closed, or Pane can't see what runs there): pass it on yourself.\"\n },\n \"decision\": {\n \"type\": \"string\",\n \"description\": \"The D-n recorded for it; absent: run `runpane inbox ack <id> --decision <D-n>`.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"inboxAckResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acknowledged\",\n \"unknown\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acknowledged\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"unknown\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"dispatchWaitResult\": {\n \"type\": \"object\",\n \"description\": \"What `--wait` prints, as one line: how the dispatched work ended. Exit codes: 0 report, 3 missing, 4 blocked, 5 exited or gone, 124 timeout. Exit 6 is indeterminate after journal replacement or history loss; inspect the panel and inbox before retrying.\",\n \"required\": [\n \"ok\",\n \"outcome\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"true only for outcome report.\"\n },\n \"outcome\": {\n \"enum\": [\n \"report\",\n \"missing\",\n \"blocked\",\n \"exited\",\n \"gone\",\n \"timeout\",\n \"indeterminate\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\",\n \"description\": \"The journal entry that ended the wait (on timeout, the position reached).\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"nextCommand\": {\n \"type\": \"string\",\n \"description\": \"Recovery command when the outcome is indeterminate.\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopInput\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"cron\",\n \"enabled\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"loop\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"cron\",\n \"enabled\",\n \"createdAtMs\",\n \"nextRunAtMs\",\n \"scheduleText\",\n \"runs\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"nextRunAtMs\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"runs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"loopId\",\n \"slotAtMs\",\n \"trigger\",\n \"status\",\n \"error\",\n \"sessionId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"loopId\": {\n \"type\": \"string\"\n },\n \"slotAtMs\": {\n \"type\": \"number\"\n },\n \"trigger\": {\n \"enum\": [\n \"schedule\",\n \"manual\"\n ]\n },\n \"status\": {\n \"enum\": [\n \"started\",\n \"failed\",\n \"skipped\"\n ]\n },\n \"error\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"sessionId\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loops\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loops\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loop\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loop\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDryRunResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"dryRun\",\n \"input\",\n \"scheduleText\",\n \"nextRunAtMs\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"input\": {\n \"$ref\": \"#/jsonSchemas/loopInput\"\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"nextRunAtMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyChoice\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The answer notify returns when the person picks this choice.\"\n },\n \"detail\": {\n \"type\": \"string\",\n \"description\": \"One more line about this choice.\"\n },\n \"recommended\": {\n \"type\": \"boolean\",\n \"description\": \"Shade this choice as the one you recommend.\"\n },\n \"was\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"--update only: the label this choice had before it was reworded. A choice whose label stays but whose detail changed may repeat its label here.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifySubQuestion\": {\n \"description\": \"One question of an options approval that asks several at once; the person picks one choice.\",\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"choices\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"notifyAction\": {\n \"type\": \"object\",\n \"required\": [\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"url\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"notifyLink\": {\n \"description\": \"A related file, page or approval shown as a chip on the card: a label and exactly one of path, url or approval.\",\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"minProperties\": 2,\n \"maxProperties\": 2,\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 80,\n \"description\": \"The chip's text.\"\n },\n \"path\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A file on the asking machine; a relative path resolves against the caller's working directory.\"\n },\n \"url\": {\n \"type\": \"string\",\n \"pattern\": \"^https?://\\\\S+$\",\n \"description\": \"An http or https page.\"\n },\n \"approval\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Another approval, by its id or Q-n; it opens in place, with Back.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyRequest\": {\n \"description\": \"What `runpane notify` and `runpane secrets request` send on the runpane:notify daemon channel: the question fields, with relays or aboutSession when they apply; resume alone; took with tookAs (and status); or update with asked, whatChanged and the changed fields. Only `secrets request` sends ui secret.\",\n \"type\": \"object\",\n \"required\": [\n \"caller\"\n ],\n \"properties\": {\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"secret\",\n \"info\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"questions\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n },\n \"description\": \"ui options, instead of choices: several questions, one pick each.\"\n },\n \"detail\": {\n \"type\": \"string\"\n },\n \"detailLabel\": {\n \"type\": \"string\",\n \"maxLength\": 40\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"action\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n },\n \"secret\": {\n \"type\": \"object\",\n \"description\": \"ui secret only: the Pane secret the answer is saved as.\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n },\n \"resume\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"took\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) whose reply the reader took; sent with tookAs and nothing but status.\"\n },\n \"tookAs\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ],\n \"description\": \"answer records the reply as the answer; question shows the reader is getting more context.\"\n },\n \"status\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"tookAs question: what the reader is doing, shown on the approval.\"\n },\n \"update\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) to save a new version of; the question fields sent replace those fields. No ui.\"\n },\n \"asked\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: the person's question, as they typed it.\"\n },\n \"whatChanged\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: what changed, one line per change.\"\n },\n \"relays\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A waiting item (an id from `runpane inbox`) to improve instead of posting a second card. ui and title are still required.\"\n },\n \"aboutSession\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Autopilot's own item: the Session it is about, an id or exact name.\"\n },\n \"returnAfterMs\": {\n \"type\": \"number\",\n \"description\": \"Return waiting after this long; set for MCP clients whose tool calls time out.\"\n },\n \"caller\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"notifyResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"answer\"\n ],\n \"properties\": {\n \"answer\": {\n \"type\": \"string\",\n \"description\": \"options: the chosen label (with questions, the picks joined with \\\" · \\\"). ask: the typed answer.\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n },\n \"answers\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"options with questions: one pick per question, in order.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The person typed a reply: it may answer the question or ask one. The approval keeps waiting; say which with `runpane notify --took <replyId> answer|question` (see next).\",\n \"required\": [\n \"reply\",\n \"id\",\n \"replyId\",\n \"next\"\n ],\n \"properties\": {\n \"reply\": {\n \"type\": \"string\",\n \"description\": \"What the person typed.\"\n },\n \"picks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"What they picked with it, one label per question.\"\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval, for --took and --update.\"\n },\n \"replyId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"This call's private id for the reply (not the id other screens show). Pass it to --took and --update: only this call was given it, so it lets its agent continue, also from outside a Pane tab.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"--took: how the reply was recorded.\",\n \"required\": [\n \"took\",\n \"id\"\n ],\n \"properties\": {\n \"took\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"delivered\"\n ],\n \"properties\": {\n \"delivered\": {\n \"enum\": [\n \"screen\",\n \"inbox\"\n ],\n \"description\": \"screen: shown on the strip. inbox: listed in the Notify inbox because a question was on screen.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person saved the value as project/name. The value is never returned.\",\n \"required\": [\n \"status\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"saved\"\n },\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person declined; nothing was saved.\",\n \"required\": [\n \"status\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"declined\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"waiting\",\n \"id\",\n \"next\"\n ],\n \"properties\": {\n \"waiting\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"unavailable\",\n \"next\"\n ],\n \"properties\": {\n \"unavailable\": {\n \"type\": \"string\",\n \"description\": \"Why nobody can answer through Pane.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"workspaceMachine\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"dnsName\",\n \"os\",\n \"online\",\n \"ips\",\n \"self\",\n \"owner\",\n \"mine\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Tailscale machine name; pass it as `machine`.\"\n },\n \"dnsName\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"ips\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"self\": {\n \"type\": \"boolean\",\n \"description\": \"This machine.\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own login.\"\n }\n }\n },\n \"workspaceListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"self\",\n \"machines\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"self\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"properties\": {\n \"workspace\": {\n \"description\": \"This machine's workspace state; null when Pane is not running.\",\n \"anyOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"enabled\",\n \"state\"\n ],\n \"properties\": {\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n },\n \"machineName\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n }\n },\n \"machines\": {\n \"type\": \"array\",\n \"items\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"required\": [\n \"joined\"\n ],\n \"properties\": {\n \"joined\": {\n \"type\": \"boolean\",\n \"description\": \"Its Pane answers workspace read, write, and exec.\"\n },\n \"info\": {\n \"type\": \"object\",\n \"required\": [\n \"hostname\",\n \"os\",\n \"isWsl\",\n \"shell\",\n \"homeDir\",\n \"wslDistros\"\n ],\n \"properties\": {\n \"hostname\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"isWsl\": {\n \"type\": \"boolean\"\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"homeDir\": {\n \"type\": \"string\"\n },\n \"wslDistros\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why it does not answer.\"\n }\n }\n }\n }\n }\n },\n \"workspaceReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"encoding\",\n \"content\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine read from; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"encoding\": {\n \"enum\": [\n \"utf8\",\n \"base64\"\n ]\n },\n \"content\": {\n \"type\": \"string\",\n \"description\": \"File text, or base64 bytes; a directory lists its entries.\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceWriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine written to; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceExecResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"os\",\n \"shell\",\n \"cwd\",\n \"exitCode\",\n \"signal\",\n \"timedOut\",\n \"stdout\",\n \"stderr\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"The command exited 0.\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"signal\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"stillRunning\": {\n \"const\": true,\n \"description\": \"It timed out and could not be stopped.\"\n },\n \"stdout\": {\n \"type\": \"string\"\n },\n \"stderr\": {\n \"type\": \"string\"\n }\n }\n },\n \"autopilotStackEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"ref\",\n \"title\",\n \"tabs\",\n \"addedAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"ref\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"description\": \"The first tab is the entry's own (its approval, document, URL, or Session).\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"addedAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"About something Autopilot wrote or asked itself.\"\n },\n \"doneAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms when it was resolved (answered, expired, or dismissed).\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"One entry on Autopilot's stack. References only: the item, file, URL, or Session it is about.\"\n },\n \"autopilotStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"enabled\",\n \"chatTabsEnabled\",\n \"entitlement\",\n \"run\",\n \"panelId\",\n \"ownerPaneId\",\n \"state\",\n \"sentinel\",\n \"stack\",\n \"walk\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"enabled\": {\n \"type\": \"boolean\",\n \"description\": \"The experimental switch (Settings › Advanced › Experimental › Autopilot).\"\n },\n \"chatTabsEnabled\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot runs as a chat tab, so it needs chat tabs on.\"\n },\n \"entitlement\": {\n \"type\": \"object\",\n \"required\": [\n \"entitled\"\n ],\n \"properties\": {\n \"entitled\": {\n \"type\": \"boolean\"\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"run\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"folder\",\n \"docsFolder\",\n \"createdAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"folder\": {\n \"type\": \"string\"\n },\n \"docsFolder\": {\n \"type\": \"string\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"panelId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"Autopilot's chat tab, once the run has one.\"\n },\n \"ownerPaneId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"state\": {\n \"enum\": [\n \"stopped\",\n \"running\",\n \"paused\"\n ]\n },\n \"sentinel\": {\n \"type\": \"boolean\",\n \"description\": \"Sentinel Mode: Autopilot keeps Sessions moving while the person is away (turning it on also starts Autopilot).\"\n },\n \"sentinelSince\": {\n \"type\": \"number\",\n \"description\": \"Epoch milliseconds when Sentinel was turned on; absent while it is off.\"\n },\n \"stack\": {\n \"type\": \"object\",\n \"required\": [\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"walk\": {\n \"type\": \"object\",\n \"required\": [\n \"sessions\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"The Session the walk is on.\"\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"waiting\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"waiting\": {\n \"type\": \"number\",\n \"description\": \"Items waiting on the person in this Session.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotWaitingResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Grouped by Session, top first, in the order Autopilot walks them.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"kind\",\n \"agent\",\n \"pane\",\n \"links\",\n \"createdAt\",\n \"ageMs\",\n \"machine\",\n \"machineId\",\n \"onStack\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"approval\",\n \"question\",\n \"permission\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Absent: not in a Session.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n },\n \"pane\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"orchestrator\": {\n \"type\": \"boolean\",\n \"description\": \"The asking tab is the Session's orchestrator.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot asked it itself.\"\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"createdAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"ageMs\": {\n \"type\": \"number\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"machineId\": {\n \"type\": \"string\"\n },\n \"onStack\": {\n \"type\": \"boolean\",\n \"description\": \"Already on the stack.\"\n },\n \"quiet\": {\n \"type\": \"boolean\",\n \"description\": \"A worker's question its orchestrator still has first call on: it shows, but doesn't ring yet.\"\n },\n \"stale\": {\n \"type\": \"boolean\",\n \"description\": \"Unanswered for 3 days: it never rings or pushes again.\"\n },\n \"connected\": {\n \"type\": \"object\",\n \"description\": \"What the item is connected to: its Session, or outside one its Pane, Autopilot's chat or its machine. Absent from an older machine.\",\n \"required\": [\n \"kind\",\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"session\",\n \"pane\",\n \"autopilot\",\n \"machine\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n },\n \"offline\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Saved machines it can't reach. Absent from an older Pane.\"\n },\n \"cantSee\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The person's Tailscale machines not saved here, so their items can't show. Absent from an older Pane.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackPushRequest\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"item\": {\n \"type\": \"string\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Absolute: runpane resolves a relative path against the caller's directory.\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Session id or exact name.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"next\": {\n \"type\": \"boolean\",\n \"description\": \"Right after the entry on screen, so it comes up next.\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"Exactly one of item, file, url, or session.\"\n },\n \"autopilotStackPushResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entry\",\n \"shown\",\n \"position\",\n \"total\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entry\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"shown\": {\n \"type\": \"boolean\",\n \"description\": \"The entry is on screen now (the stack had nothing open).\"\n },\n \"position\": {\n \"type\": \"number\",\n \"description\": \"1-based place on the stack.\"\n },\n \"total\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot answer` sends on the runpane:autopilot:answer daemon channel.\",\n \"required\": [\n \"id\",\n \"answer\",\n \"because\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"answer\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"choice\"\n ],\n \"properties\": {\n \"choice\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"text\"\n ],\n \"properties\": {\n \"text\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"because\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A D-n, a document or the person's words.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot close` sends on the runpane:autopilot:close daemon channel.\",\n \"required\": [\n \"id\",\n \"reason\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot rewrite` sends on the runpane:autopilot:rewrite daemon channel. Link paths are absolute: runpane resolves a relative path against the caller's directory.\",\n \"required\": [\n \"id\",\n \"ui\",\n \"title\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"description\": \"The new item's id.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"pausedMark\": {\n \"type\": \"object\",\n \"required\": [\n \"at\",\n \"by\"\n ],\n \"properties\": {\n \"at\": {\n \"type\": \"string\",\n \"description\": \"When the tab was paused (ISO 8601).\"\n },\n \"by\": {\n \"enum\": [\n \"manual\",\n \"auto\"\n ],\n \"description\": \"manual: a person or agent paused it. auto: auto-pause paused it after idleHours without use.\"\n },\n \"idleHours\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"pauseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"action\",\n \"scope\",\n \"panelIds\",\n \"unchanged\",\n \"failed\",\n \"message\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when a tab could not be resumed; the others still were.\"\n },\n \"action\": {\n \"enum\": [\n \"paused\",\n \"resumed\"\n ]\n },\n \"scope\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"panel\",\n \"pane\",\n \"session\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Tabs this call paused or resumed.\"\n },\n \"chatTabsNotPaused\": {\n \"type\": \"number\",\n \"description\": \"On a pause: chat tabs in scope, which can't pause yet, so their agents keep running.\"\n },\n \"unchanged\": {\n \"type\": \"number\",\n \"description\": \"Terminal tabs in scope that were already paused (for pause) or not paused (for resume).\"\n },\n \"failed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"error\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, then route by the rule 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: if the work's branch already has a Pane (find it with `runpane panes list --repo <repo> --json` by matching the PR's head branch against `git -C <worktreePath> branch --show-current`), open a new agent tab in it with `runpane panels create --pane <id> --agent <agent> --initial-input \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`; only when the branch has no Pane yet, create one with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` (the new Pane is pinned by default; add `--no-pinned` for a throwaway Pane, add `--base-branch <ref>` to branch from something other than the repository default, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`. For long-lived supervision, use `runpane watch --follow` instead of polling wait or screen.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use `panes create` only when the work's branch has no Pane yet (new work, or an existing PR with no Pane), since each new Pane creates a worktree. Every later agent for that branch opens as a new tab in its Pane with `panels create --pane <id>`, never a new Pane. Tabs share one checkout: only the implementer changes the branch or HEAD, one writer at a time; review, audit and QA tabs never run `git checkout`, `reset` or `stash`.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: Pane's skills live in `<PANE_DIR>/skills/pane-chat/skills/`, and the Pane Chat entry point is `<PANE_DIR>/skills/pane-chat/pane-orchestrator/SKILL.md`; read the skill that matches the request, such as `discussion`, `create-ticket`, `tdd`, `prepare-pr`, or `review`.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; `panes create` pins the new Pane into the UI's favorite/pin set by default, so no follow-up `panes pin` call is needed and `--no-pinned` is the opt-out for throwaway Panes.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"Read and change a repo's Project Settings (build, run, and archive scripts, worktree folder, environment variables) with `runpane repos get` and `runpane repos update`; never edit Pane's database by hand.\",\n \"When a command needs a secret (an API key, a token), run it with `runpane secrets exec --project <project> -- <command>`. If the secret is missing, ask the person with `runpane secrets request`; never ask them to paste a secret into the chat, and never print one.\",\n \"If WSL cannot reach Pane, the user may be running Windows Pane; try `/mnt/c/Users/<you>/.pane/bin/runpane doctor --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude|cursor> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes, wait for each agent's input box, and verify its starting message in one call.\",\n \"Use `runpane panels screen` for compact current state, including Claude and Codex composer state. Use `panels wait` for create-time readiness or text checks. Use `runpane watch --follow` to block on workspace transitions (READY, BLOCKED, IDLE, STUCK, EXIT) without polling, or `runpane watch --until-event` to exit on the first one. Use `panels submit` to send and submit a new turn. Use `panels submit-composer --strategy auto` only for a composer that was filled separately.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"Pane terminals draw inline images: sixel, iTerm2 inline images, and the kitty graphics protocol. Tools that need kitty graphics, such as terminal-browser and terminal-doom, run inside a Pane panel; `runpane doctor --json` reports the exact list under `terminal.graphicsProtocols`.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen`. For a harness that consumes streaming output, use `runpane watch --follow` (with `--session <id>` for a Session); its named consumer applies settling and batching. Adding numeric --since leaves it unnamed and bypasses cadence; --as cannot be combined with --since. A harness that wakes only when a process exits uses `runpane watch --as <name> --until-event --json` and runs it again after each event. Single-event waits deliver immediately without follow cadence. Never pipe `--follow` into `grep -m1` or `head`.\",\n \"When several agents share one resource, such as a test account, name a lock in their prompts: each worker runs `runpane lock acquire --name <name> --ttl 30m --wait 1800000 --json` before using it and `runpane lock release --name <name>` after.\",\n \"Loops (`runpane loops create`) run a task on a cron schedule, each run in a new Pane or, with `--session`, as a message to a Session's agent. They are for large recurring jobs, such as a nightly bug sweep, and run at most once an hour. For a short wait, use your agent harness's own sleep and wake instead.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude|cursor>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--build-script <script>\",\n \"--run-script <script>\",\n \"--archive-script <script>\",\n \"--worktree-folder <path>\",\n \"--env <json>\",\n \"--env-remove <name,...>\",\n \"--name <name>\",\n \"--clear <setting,...>\",\n \"--from-json <path|->\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List Pane secrets: project, name, time and machine. Never a value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment.\",\n \"arguments\": [\n \"--project <project>\",\n \"--only <name,...>\",\n \"--env <VAR=NAME>\",\n \"--file <name>\",\n \"-- <command>...\"\n ]\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card; returns saved or declined, never the value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--name <name>\",\n \"--title <text>\",\n \"--reason <text>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--pinned\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--session <id|name>\",\n \"--merged\",\n \"--closed\",\n \"--source <user|agent>\",\n \"--force\",\n \"--remove-worktree\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--name <new-name>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally a panel) on explicit user request.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--panel <panel-id>\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open reviewer, QA, fix or helper agent tabs inside an existing Pane; they share that Pane's worktree. The default for any later agent on existing work.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Show the user an HTML page, plan, report, dev server URL, or file as a tab in split view beside the agent.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--url <url>\",\n \"--file <path>\",\n \"--title <title>\",\n \"--split\",\n \"--tab\",\n \"--no-focus\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened once it no longer matters; refuses the person's tabs, busy agents and unsent input.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--dry-run\",\n \"--force\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the key for its current state.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter|tab>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place (same panel id), resuming its agent's conversation.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--session <id|name>\",\n \"--all-stopped\",\n \"--concurrency <count>\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace events from the durable daemon journal; follow uses conservative cadence and quiet expiry by default.\",\n \"arguments\": [\n \"--follow\",\n \"--until-event\",\n \"--session <id|name>\",\n \"--self-test\",\n \"--kinds <kind,...>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question wherever they are and wait for the answer; prefer it to asking in the terminal.\",\n \"arguments\": [\n \"--ui <options|approve|ask|info>\",\n \"--title <text>\",\n \"--choices <json>\",\n \"--links <json>\",\n \"--resume <id>\",\n \"--json\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update Pane and the active supported CLI with durable status.\",\n \"details\": \"Defaults to both components. Use --pane-only or --cli-only to select one. Preflight checks installation ownership and supervisor capability before downtime.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Requested app and CLI release version.\"\n },\n {\n \"name\": \"--pane-only\",\n \"required\": false,\n \"description\": \"Update only the installed Pane app.\"\n },\n {\n \"name\": \"--cli-only\",\n \"required\": false,\n \"description\": \"Update only the invoked supported CLI installation.\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<operation-id>\",\n \"required\": false,\n \"description\": \"Read durable status without a running Pane.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Wait until the operation completes or requires attention.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Inspect preflight without staging or stopping Pane.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Requires standalone Node and a current-user OS supervisor. npm, managed CLI, and supported pip environments retain their actual ownership. System-package Linux and installations requiring elevation are refused before downtime.\",\n \"The operation preserves a pre-migration data snapshot; it never blindly rolls an old app or database over migrated state.\",\n \"The initial result can be accepted or staging; it is not a claim that the update or restoration completed.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Accepted and ignored, so one --pane-dir can be passed to every runpane command.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n },\n {\n \"name\": \"--report\",\n \"required\": false,\n \"description\": \"Prepare a redacted, inspectable diagnostic report.\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Report title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm creating exactly one greenfield-inc/Pane issue.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\",\n \"runpane doctor --report --title \\\"runpane watch failed\\\" --body-file ./watch.txt --json\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"daemon repair\": {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"details\": \"Use this only after doctor reports a stale or broken remote-daemon launcher. The command invokes the installed Pane executable in service-repair mode and does not rerun remote setup.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Remote daemon data directory; defaults to ~/.pane_remote.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Existing Pane executable used to perform repair.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip the service restart confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print one machine-readable repair result.\"\n }\n ],\n \"examples\": [\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"notes\": [\n \"Repair does not create pairing credentials, change tunnel configuration, or download Pane.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\",\n \"agentContextUnknownCommandError\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\",\n \"An unknown `--command` exits 2. With `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout, where `candidates` lists the closest command names first; without `--json` the same candidates go to stderr.\",\n \"`--pane-dir <path>` is accepted and ignored, so one `--pane-dir` can be passed to every runpane command.\"\n ]\n },\n \"mcp\": {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"details\": \"Use this to register Pane with an MCP client. Pane registers it with Claude Code, Codex, and Cursor automatically; each tool mirrors one runpane command and returns its --json result.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated toolsets to serve: core (default), all, read, repos, panes, panels, agents, sessions, loops, workspace, git, docs, links, admin.\"\n },\n {\n \"name\": \"--read-only\",\n \"required\": false,\n \"description\": \"Serve only read-only tools.\"\n }\n ],\n \"examples\": [\n \"claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \"codex mcp add pane -- npx --yes runpane@latest mcp\"\n ],\n \"notes\": [\n \"Only the npm package and the Pane app include the MCP server; the Python package prints how to run it with Node.\",\n \"Commands that need --yes in the CLI take `yes: true` as a tool input.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry with `/mnt/c/Users/<you>/.pane/bin/runpane repos list --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"repos get\": {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"details\": \"Use this before changing a repo's settings, or to find out what a new Pane will run. Each script reports `value` (saved in Project Settings), `fallback` (from the repo's config file), and `effective` (what Pane runs: the saved value when set, otherwise the fallback). `envVars` lists the project environment variables: plain values in full, keychain items and Pane secrets as references, never their values.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name. Defaults to active.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable settings.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos get --repo Pane --json\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"notes\": [\n \"Config-file fallbacks are read from the repository root. At run time Pane reads them from each Pane's worktree, so a branch that changes its config file can run something else.\",\n \"`currentBranch` is detected from the repository; it is not a setting.\"\n ]\n },\n \"repos update\": {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"details\": \"Saves through the same path as the Project Settings dialog: Pane drops its cached project context, rebuilds the run commands, and open windows reload the repo. Settings you do not pass keep their value. Clear a setting with `--clear` or an empty value; a cleared script falls back to the repo's config file. --env adds or replaces project environment variables by key, and --env-remove removes them. A variable is plain text, an existing macOS keychain item, or a Pane secret reference; to store a new secret value, use `runpane secrets request` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Repository display name. It cannot be cleared.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Project variables to add or replace by key: a JSON array of {key, source: \\\"plain\\\", value}, {key, source: \\\"keychain\\\", service, account} or {key, source: \\\"secret\\\", project, name}.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"required\": false,\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"JSON settings object with keys name, buildScript, runScript, archiveScript, worktreeFolder, openIdeCommand, systemPrompt, and envVars ({set: [...], remove: [...]}); null clears. Flags override it.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Return the settings as they would be after the update without saving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --yes --json\",\n \"runpane repos update --repo Pane --clear archive-script --dry-run --json\",\n \"runpane repos update --repo Pane --from-json settings.json --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"notes\": [\n \"`changed` lists the settings whose saved value differs from before.\",\n \"The worktree folder applies to Panes created after the update; existing worktrees stay where they are.\",\n \"Plain values are saved and shown in full; do not put a secret in a plain variable. Point the variable at a Pane secret instead.\"\n ]\n },\n \"repos rename\": {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"details\": \"Use this to replace an automatic repository name, such as one with a numeric suffix, with a clear one. It changes only the display name.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository to rename: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"New non-empty repository name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated repository.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos rename --repo 3 --name Lev --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"notes\": [\n \"The repository path, Panes, and settings are unchanged.\"\n ]\n },\n \"secrets list\": {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"details\": \"Use this to find the project and name to pass to `runpane secrets exec`. Each entry has project, name, updatedAt and machine. storage says how this machine keeps values; encrypted false means a plain 0600 file on a machine without an OS keyring.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": false,\n \"description\": \"Only this project's secrets.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets list --json\",\n \"runpane secrets list --project stripe --json\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"notes\": [\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets set\": {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"details\": \"The value comes only from stdin, so it never appears in argv or a transcript. One trailing newline (\\\\n or \\\\r\\\\n) is dropped; an empty value is refused. If stdin is a terminal it is refused too: pipe the value in, or have the person use Settings > Secrets or `runpane secrets request`. With --json it prints {ok, replaced, secret} (secretSetResult).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretSetRequest\",\n \"secretSetResult\"\n ],\n \"notes\": [\n \"Never set a value someone pasted into the chat; ask with `runpane secrets request` instead, so the value stays out of the transcript.\",\n \"There is no MCP tool for this command: MCP arguments land in the transcript.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets delete\": {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"details\": \"Deletes <project>/<name> here. Each other machine deletes it the next time a desktop or the phone syncs with it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"notes\": [\n \"A secret that does not exist fails with SECRET_NOT_FOUND.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets exec\": {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"details\": \"Reads the secrets from this machine's Pane when it runs, starts the command with your environment plus the secrets, passes stdin, stdout and stderr through, and exits with the command's exit code. By default it injects every secret in the project under its own name. --only limits that to the names given. --env VAR=NAME injects secret NAME as VAR. --file NAME writes secret NAME to a 0600 file in a private temp folder and sets NAME to the file's path; the file is removed when the command exits. Injected names override your environment.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Inject only these secrets, comma-separated.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME>\",\n \"required\": false,\n \"description\": \"Inject secret NAME as VAR. Repeatable.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Write secret NAME to a private temp file and set NAME to its path. Repeatable.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command to run and its arguments.\"\n }\n ],\n \"examples\": [\n \"runpane secrets exec --project stripe -- pnpm test:webhooks\",\n \"runpane secrets exec --project stripe --env STRIPE_API_KEY=STRIPE_SECRET_KEY -- node scripts/sync.js\",\n \"runpane secrets exec --project gcp --file SERVICE_ACCOUNT_JSON -- sh -c 'gcloud auth activate-service-account --key-file \\\"$SERVICE_ACCOUNT_JSON\\\"'\"\n ],\n \"notes\": [\n \"Target names PATH, WORKTREE_PATH and PANE_*, in any case, are refused with SECRET_RESERVED_NAME, and nothing runs.\",\n \"A missing secret fails with SECRET_NOT_FOUND before anything runs; ask for it with `runpane secrets request`.\",\n \"Do not echo a secret from inside the command: its output lands wherever the command's output goes, such as your transcript.\",\n \"There is no MCP tool for this command; run it in a shell.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\",\n \"Secrets are the machine owner's: every secrets command in a teammate's `runpane workspace exec` (PANE_TEAMMATE_LOGIN set) fails with SECRET_OWNER_REQUIRED.\"\n ]\n },\n \"secrets request\": {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"details\": \"Shows a Notify card with your title and reason, a masked field, Save securely and Decline. What the person types is saved on this machine as the Pane secret <project>/<name> and synced to their other machines. You get back {status: \\\"saved\\\", project, name} or {status: \\\"declined\\\"}, never the value. Then use it with `runpane secrets exec`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project, such as the service or repo it belongs to: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name, such as STRIPE_SECRET_KEY: letters, digits and underscores, not starting with a digit.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What the secret is, in a few words, such as \\\"Stripe test secret key\\\".\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What you need it for, in one sentence.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook integration tests against Stripe's test mode.\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"A result of {waiting: true, id} means the card is still open and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now. Do not ask for the secret in the chat; follow next.\",\n \"Saving replaces a secret that already exists under that name; the card says so.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\",\n \"`status` is `running` while a tool or agent terminal is live, otherwise the stored lifecycle value (`error` and `initializing` always show as stored). The Pane's dock shell (`dock: true` in `panels list`) does not count, so a Pane whose only live terminal is the dock reads its stored value, usually `stopped`. `agentState` is the most urgent state of the live agent panels: `blocked`, `working`, `ready`, or `none`. `agentStatus` (`active`/`idle`) is kept for older clients.\"\n ]\n },\n \"panes cost\": {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"details\": \"Use this to attribute estimated workspace token costs to Pane lifetimes and inspect the models and cache efficiency behind each Pane's spend.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Return only the selected Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes cost --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"notes\": [\n \"The report range is the last 30 days.\",\n \"Costs are estimates based on API token prices.\",\n \"The unattributed bucket reconciles the Pane rows against totals in the unscoped result.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use this only when the work's branch has no Pane yet: new work, or an existing PR with no Pane. Each new Pane creates a worktree that checks out, installs and often builds, so creating several at once can freeze the machine. First find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`. For review, follow-up review, fixes, QA or any other agent on work that already has a Pane, use `runpane panels create --pane <id>` to open a new agent tab in that Pane instead. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch the new worktree from this ref (for example origin/main or release/2.4) instead of the repository default.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree and branch name; defaults to --name. Characters outside [a-z0-9-] are removed.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"required\": false,\n \"description\": \"Ref to branch from, such as origin/main or release/foo. --base is an alias.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Exact new branch name, slashes included (for example agents/w5a). Fails if the branch exists; never renamed to be unique. Defaults to the worktree name.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree directory name; defaults to --name.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--pinned\",\n \"required\": false,\n \"description\": \"Accepted for backward compatibility and now a no-op; `panes create` already pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Create the pane unpinned instead of the default pinned (the Pane UI's favorite/pin star). Pinning never implies focus.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name w5a --base origin/main --branch agents/w5a --agent <agent> --prompt-file prompt.md --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent claude --initial-input-file brief.md --as-file-pointer --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: before creating one, find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`, and add a tab with `panels create --pane <id>` when it exists.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"`panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`: Pane creates the worktree on exactly that branch name. `--branch` is checked with `git check-ref-format --branch` and fails if the branch already exists; `--worktree-name` still names the directory.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved. `panes create` pins the new Pane by default, so a follow-up `panes pin` call is unnecessary; pass `--no-pinned` for throwaway shells or bulk imports. `--pinned` is still accepted and is now a no-op. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input. A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"With --wait-ready, `initialInput.delivery` reports `taken`, `queued`, `in-composer` or `unknown` with its evidence (`transcript`, `screen`, or `argv` for a prompt passed at launch); verifiedSubmitted is true for `taken` or `queued`. Routing initial input alone does not imply verified submission, and the create's `ok` is false unless a Claude or Codex starting message is verified.\",\n \"If initialInput.blocked.kind is submission_unverified, do not submit again automatically. Inspect initialInput.staged and attempts, then run nextCommand to resolve the ambiguous composer state.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, use Pane's own `runpane` (see doctor) and select the saved WSL repo by name or id, for example `runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json`.\",\n \"Use --wait-ready when an agent needs the started agent at its input box, and its starting message verified, instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"At most 2 Panes run setup at once (worktree file sync, dependency install, build script); more wait in line. A queued `panes create` returns the Pane id right away with `setupQueue` ({ position, message }) and no `panelId`; the agent starts when setup finishes. Find its panel later with `runpane panels list --pane <id> --json`.\",\n \"A Pane with its own worktree starts at the remote's current tip of a remote-tracking base (the default `origin/main`, or one named with `--base`): Pane fetches that branch first, waiting at most about 10 seconds, and creates started together share the fetch. Each item's `base` reports `{ ref, commit, refresh }`, where `refresh` is `fetched`, `recent` (reused a fetch from the last 30 seconds), `skipped` (a local branch or commit base) or `failed`. A failed refresh never fails the create; the Pane starts from the local ref and `base.notice` says what happened and how to catch up.\",\n \"`panes create --branch <name>` fails with code `ERR_RUNPANE_BRANCH_HAS_PANE` when another Pane already has that branch checked out. The message names the Pane and gives the next step: `runpane panels create --pane <id> --tool-command \\\"<cmd>\\\" --source agent --no-focus`.\"\n ]\n },\n \"panes adopt\": {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane.\",\n \"details\": \"Validates that the canonical path is a registered worktree of the selected saved repository, then creates a Pane record without creating, syncing, building, removing, or recreating the directory.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Saved repository selector.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<dir>\",\n \"required\": true,\n \"description\": \"Existing git worktree path.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Agent whose terminal to create; with --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom command to stage or launch instead of a built-in agent, such as a wrapper that runs one.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Persist this agent session id and stage its resume command.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Top-level sidebar folder.\"\n },\n {\n \"name\": \"--launch\",\n \"required\": false,\n \"description\": \"Run the agent (or its resume command) immediately.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sent once the launched agent is ready. Requires --launch.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Alias for --initial-input-file; read the prompt from a file or stdin. Requires --launch.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for the launched agent to be ready and the prompt submitted before returning. Requires --launch.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Do not pin the adopted pane.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the adopted pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview only.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent codex --resume <id> --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --tool-command \\\"agent-farm run free-range\\\" --agent claude --launch --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent claude --launch --prompt-file prompt.md --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"The resume command is staged without Enter unless --launch is passed.\",\n \"Archiving never deletes an adopted worktree unless `runpane panes archive --remove-worktree` is used.\",\n \"One of --agent or --tool-command is required.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"--resume needs a built-in agent command; a wrapper command resumes its own way.\",\n \"The adopted Pane reports `status: running` in `panes list` while its tool terminal is live.\",\n \"A prompt (--prompt, --initial-input, --prompt-file, --initial-input-file) or --wait-ready needs --launch; without it adopt fails instead of dropping the prompt.\",\n \"With --launch the result carries the same readiness, initialInput, and nextCommand fields as panes create. With --resume the prompt goes to the resumed conversation's composer once the agent is ready.\",\n \"To start new work on a named branch, prefer `panes create --base <ref> --branch <name>` over `git worktree add` plus adopt.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. The safety check refreshes the configured upstream before comparing it to HEAD and includes exact unpushed commit IDs and subjects. When the branch has no upstream or its upstream is gone (GitHub deletes merged branches), a merged pull request whose head is HEAD counts as pushed, so squash-merged branches archive without --force. Refuses unsafe archive operations unless --force is used. Use --dry-run to inspect the same evidence without mutating Pane state. Use --session <id|name> --merged to close out every finished Pane of a Session at once. Archive also refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless --force is used, since archiving stops the agent or loses the input. Add --closed to the Session sweep to also archive Panes whose PR closed without merging.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane/session id to archive. Required unless --session is used.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn or waiting for an answer (archiving stops it), or a tab holds unsent input (archiving loses it).\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"required\": false,\n \"description\": \"Also check and remove an adopted Pane's worktree. Pane-managed worktrees are always removed; the local branch is always kept.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Archive the Panes associated with this named Session instead of one Pane. Requires --merged.\"\n },\n {\n \"name\": \"--merged\",\n \"required\": false,\n \"description\": \"With --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work; skip the rest with a reason and next step. An open PR is skipped as pr-open, no PR or a closed unmerged PR as pr-not-merged, and a PR state GitHub cannot confirm as pr-status-unknown.\"\n },\n {\n \"name\": \"--closed\",\n \"required\": false,\n \"description\": \"With --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks. Without --closed they are skipped as pr-not-merged.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Refresh and print archive safety evidence without archiving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --dry-run --json\",\n \"runpane panes archive --pane <pane-id> --remove-worktree --yes --json\",\n \"runpane panes archive --session <id|name> --merged --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --closed --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --remove-worktree --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"Inspect safetyCheck.unpushedCommitDetails for exact commit IDs and subjects; upstreamRefreshed confirms the tracking ref was refreshed first.\",\n \"A --dry-run result sets dryRun:true and wouldArchive without deleting the Pane or its worktree.\",\n \"A successful archive reports worktreeCleanup `completed` once the worktree is gone from its path and from git. trashDeletion `pending` means a large worktree (for example one with node_modules) is still being deleted from .git/pane-trash in the background; that is still ok:true. `timeout` (also ok:true) means removal is still running; only `failed` sets ok:false.\",\n \"safetyCheck.mergedViaPr names the merged PR whose head is HEAD; it needs an authenticated `gh`. Without it, a squash-merged branch whose remote branch was deleted reports unpushed-commits.\",\n \"An adopted Pane (panes adopt) keeps its worktree unless --remove-worktree is passed. `panes restore` does not recreate a removed adopted worktree; the local branch is kept, so `git worktree add <path> <branch>` brings it back.\",\n \"With --session --merged, each item has outcome archived, would-archive (dry run), skipped (with skipped.code and skipped.message), or failed.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\",\n \"Undo an archive with `runpane panes restore --pane <pane-id> --yes`, which recreates the worktree. `runpane links create --pane <pane-id>` gives the user a link to review the Pane first.\",\n \"When the safety check is skipped, `safetyCheck.reason` says why: `external-worktree` (an adopted Pane whose worktree Pane does not own), `main-repo`, `missing-project-context`, or `git-error`. `worktreeWillRemain: true` means archiving leaves the worktree on disk.\",\n \"An agent mid-turn or waiting for an answer blocks archive with `agent-busy` (`safetyCheck.busyAgents` lists them); wait until it is idle with `runpane panels wait --panel <id> --for idle`, then archive again. The Session sweep skips such a Pane with skipped.code `agent-busy`.\",\n \"A tab holding text typed into an agent's composer but not sent, or an editor with unsaved edits, blocks archive with `unsent-input` (`safetyCheck.unsentInput` lists them), the same input `panels close` refuses to lose; send, save or clear it, then archive again. `--force` discards it.\"\n ]\n },\n \"panes pin\": {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to pin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pin --pane <pane-id> --yes --json\",\n \"runpane panes pin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.\",\n \"Pinning does not focus the Pane.\"\n ]\n },\n \"panes unpin\": {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to unpin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes unpin --pane <pane-id> --yes --json\",\n \"runpane panes unpin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.\",\n \"Unpinning does not focus the Pane.\"\n ]\n },\n \"panes pause\": {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"details\": \"Pauses every terminal tab of a Pane, run scripts included, until `panes resume` resumes it. A chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`. A Pane reads as paused (`panes list` `paused`) while all of its terminal tabs are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pause --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Find idle Panes with `panes list --json`: `inactiveSince` is the latest terminal output or input in its tabs, or the last time the person selected it.\",\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panes resume\": {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"details\": \"Resumes a Pane's paused tabs. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes resume --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"panes rename\": {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"details\": \"Trims and sets the Pane display name through the same persistence and session-updated event path used by the UI. Dry-run returns the projected updated pane without mutating.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to rename.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<new-name>\",\n \"required\": true,\n \"description\": \"New non-empty Pane/session name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and return the projected updated pane without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated pane.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"notes\": [\n \"Empty and whitespace-only names are rejected; no additional maximum length is imposed beyond the existing UI and database behavior.\",\n \"The JSON result includes the updated pane summary so callers do not need a second panes list request.\"\n ]\n },\n \"panes focus\": {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels).\",\n \"details\": \"Brings the Pane desktop window to the foreground and selects the requested Pane exactly like clicking it in the sidebar; when --panel is given it also selects that panel/tab inside the Pane. This steals window focus, so it must only be run on an explicit user request to open, focus, or show a Pane, never proactively. Focusing does not change the Pane's worktree, branch, panels, or pinned state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to focus. Must be an active (non-archived) Pane.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Optional panel/tab id inside the Pane to select; must belong to the focused Pane.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change focus behavior.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands; required in non-interactive contexts because focusing steals window focus.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes focus --pane <pane-id> --source user --yes --json\",\n \"runpane panes focus --pane <pane-id> --panel <panel-id> --source user --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"notes\": [\n \"Run this only when the user explicitly asks to open, focus, show, or switch to a Pane; never focus a Pane proactively because it steals the user's window focus.\",\n \"Archived Panes cannot be focused; the command fails clearly instead of falling back to focusing another Pane.\",\n \"If --panel does not belong to the focused Pane, the command fails clearly rather than selecting a different panel.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\",\n \"`agentType` is the agent Pane treats the panel as; `agentDetection` says how it knows (`declared`, `command`, `process`, or `screen`), and `launchCommand` is the command the panel was launched with.\",\n \"`resumeId` is the agent session ID Pane saved for reopening the panel, and `resumeSource` says which rule resumes with it: `agent-farm` (an `agent-farm run` or `agent-farm agent` command), `configured` (the panel's own resume command), or `agent` (a built-in agent's captured session). Both are omitted when no ID is saved.\",\n \"Every Pane has one plain shell titled `Terminal`, its terminal dock: `dock: true` marks it, and the field is omitted on every other panel. Setup progress notices appear there; it is not a tool or agent tab and does not count toward the Pane's `status`. A `--tool-command` tab without `--title` or `--agent` is titled from its command (whole words up to 40 characters).\",\n \"`openedBy` says who opened the tab through runpane: `source` (`agent` or `user`) and, when known, `panelId` of the tab that opened it. It is omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose `openedBy.source` is `agent`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters. A chat tab has no terminal: input fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated key names to press in order: enter, escape, tab, backspace, space, up, down, left, right, ctrl-c, ctrl-d, or any single character.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\",\n \"runpane panels input --panel <panel-id> --keys down,enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"To answer a menu or prompt, prefer `--keys` (for example `--keys down,enter`) over escape sequences in `--text`.\",\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. On a chat tab it returns the newest transcript lines with source acpTranscript. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"The composer object reports whether a Claude or Codex composer is present and whether it holds undelivered text. Placeholder and suggestion text (drawn dim or grey, such as Claude's suggested next prompt) does not count as undelivered text; `composer.ghostText` carries it, and text output marks its line with `⟨suggestion⟩`.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"details\": \"Use this for ordinary interactive submissions. For Claude, Pane waits for the composer to appear and show the staged text, then sends Enter on its own and verifies that the composer cleared; Claude keeps an Enter that arrives together with the text as a newline. For a Codex composer, Pane stages the text, waits for paste handling, then queues with Tab while working or submits with Enter when ready, and verifies delivery when visible evidence is available. Wrapper-launched agents are handled the same way once Pane knows the agent (declared, from the foreground process, or from the screen). A Claude or Codex panel with no composer on screen gets nothing: the result is `ok: false` with `blocked.kind: composer-unknown`. Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working. Other terminals receive a normalized CR Enter. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--interrupt\",\n \"required\": false,\n \"description\": \"Interrupt the current turn and wait up to 30 seconds for an empty idle composer before delivering. Escape for Codex/Claude; Ctrl+C for Cursor.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --input-file prompt.md --as-file-pointer --wait --yes\",\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\",\n \"runpane panels submit --panel <panel-id> --input-file brief.md --as-file-pointer --yes --json\",\n \"runpane panels submit --panel <panel-id> --text \\\"Stop that approach and follow this correction\\\" --interrupt --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes sequenceName, verifiedSubmitted, delivery and nextCommand. For Claude and Codex, `delivery.state` is `taken` (the agent started a turn with the text), `queued` (it holds the text until its current turn ends), `in-composer` (the text is still in the composer) or `unknown`, and `delivery.evidence` is `transcript` (the agent's own transcript) or `screen`. verifiedSubmitted is true for `taken` or `queued`; never resend those. If ok is false, inspect blocked and do not assume the turn started.\",\n \"Do not follow `panels submit` with `panels submit-composer`; Claude and Codex composer submission is handled atomically.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\",\n \"On `blocked.kind: composer-unknown`, nothing was typed. Check `panels screen`; if the agent is at a prompt Pane does not recognise, use `panels input`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"With --interrupt, Pane sends the stop key only while the agent is active, waits for an empty idle composer, then submits. If readiness times out, replacement text is not sent. A queued result is not read yet: delivery.message explains the delay; never resend queued text. Interrupt success requires delivery.state: taken. Unknown agents and occupied composers are rejected without typing.\",\n \"On a chat tab, submit sends an ACP prompt and returns sequenceName=acp, enter=none, operationId and acp-receipt delivery evidence; a prompt sent while the agent works is queued and sent next. Do not blindly resend after a lost response.\"\n ]\n },\n \"panels resume\": {\n \"name\": \"panels resume\",\n \"summary\": \"Restart stopped terminal panels in place, resuming their agents' conversations: one panel, a Session's panels, or every stopped agent panel.\",\n \"details\": \"Use this when `panels list`, `panels screen`, or `panels wait` report `running: false`, or `watch` reports `panel.stopped`, typically after a Pane restart or once its agent exited back to the shell. The panel keeps its id. Pane re-runs the panel's saved launch command resolved to the agent's resume command (Claude `--resume <session>`, `codex resume <session>`, Cursor `--resume`), the same restart the app runs when it shows the panel. It never takes focus. A panel whose agent exited back to its shell has its saved command run again in that shell. On a running panel it does nothing and returns `action: \\\"already-running\\\"`. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope (plain shells and paused tabs are left alone), `--concurrency` at a time, skips running panels, and returns one result per panel; one failure does not stop the others.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Terminal panel id. Give exactly one of --panel, --session, or --all-stopped.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of this Session's associated Panes.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of every non-archived Pane.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10. With --wait-ready the resumed panels then wait together, so the call takes the restarts plus one --ready-timeout-ms.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the resumed agent is ready, with the same startup-screen checks as `panels create --wait-ready`.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"notes\": [\n \"If readiness.blocked is present, the agent is at a startup prompt (trust, update, [y/n]); inspect `panels screen` or follow blocked.suggestedCommand. Ask the user before answering trust or permission prompts.\",\n \"A Claude panel resumes the conversation whose session id Pane launched it with; a Codex panel resumes the session id Pane captured from its output. Without one, the panel relaunches its original command.\",\n \"A bulk resume is ok only when every panel resumed and, with --wait-ready, became ready. Check items[].readiness.blocked and items[].error per panel.\"\n ]\n },\n \"panels close\": {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"details\": \"Closes a tab through the same path as closing it in Pane, so a terminal tab's process stops. It never archives the Pane or touches its worktree. Only a tab an agent opened can close: Pane records the opener when a tab is opened with `--source agent` (panels open, panels create, panes create, agents start). It refuses, without closing, a tab not opened by an agent (the person's, and every tab from before Pane recorded openers), a permanent tab, an agent mid-turn or waiting for an answer, unsent composer text or unsaved editor edits, and a live tab whose input Pane cannot read (agents other than Claude and Codex, and chat tabs). A refusal exits 1 with `refused.code`, `refused.message` and `refused.next`. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports the same without closing. `--force` (only for a stuck tab, or one the person asked you to close) closes it anyway, as the tab's close button does: a busy agent's turn and unsent input are lost, and the result says `forced: true`. A permanent tab still stays.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tab to close.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Close it even if an agent didn't open it, its agent is busy, or it holds unsent input. Only for a stuck tab, or one the person asked you to close.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels close --panel <panel-id> --dry-run --json\",\n \"runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"notes\": [\n \"ok:false with `refused` means the tab was NOT closed. `refused.code` is opened-by-user, permanent, agent-busy, unsent-input, or input-unknown; follow `refused.next`. Use --force only when the tab is stuck or the person asked you to close it.\",\n \"`alreadyClosed: true` (ok:true, closed:false) means no tab has that id any more.\",\n \"With --dry-run, `wouldClose` says whether the close would go ahead and `refused` says why not. Nothing closes; a refused dry run also exits 1.\",\n \"Open tabs with `--source agent` so they count as yours; `panels list` shows who opened each tab as `openedBy`.\"\n ]\n },\n \"panels pause\": {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"details\": \"Pauses one terminal tab until `panels resume --panel <panel-id>`, or its Pane or Session, resumes it. A chat tab can't pause yet: pausing one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE` and changes nothing. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels pause --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command. On a chat tab, a text wait (`--for text`) searches only what the agent writes after the wait starts, plus a reply still streaming then, never the person's prompts.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"If stopped is true, the panel's terminal is not running (for example after a Pane restart); run nextCommand (`panels resume`) instead of waiting again.\",\n \"The default timeout and screen are intentionally small for agent context safety.\",\n \"On a chat tab, status comes from the host ACP session (a turn that ended is ready, a pending approval or question is blocked) and the screen field has source=acpTranscript: transcript text, not a terminal screen.\"\n ]\n },\n \"panels last-message\": {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"details\": \"Use this when a worker is READY and you need what it said, such as its PR number or result. It reads the Claude or Codex transcript Pane found for the panel and never falls back to the screen.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Agent panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum characters to return; defaults to 20000. The end of the reply is kept.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels last-message --panel <panel-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"notes\": [\n \"Without a transcript (a shell panel, another agent, or no reply yet) the result is `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and the command exits 1. Use `panels screen` then.\",\n \"A worker that ran `runpane report` already handed back its state, PR, head, and summary; read those with `agents status` before reading the whole reply.\"\n ]\n },\n \"panels conversation\": {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"details\": \"Pane calls this for you: the Claude Code SessionStart hook that Agent Farm and Pane's plain claude tabs install runs `runpane panels conversation --hook` on start, /resume, /clear and compact. Run it yourself only to tell Pane a panel is on a conversation it could not learn from the hook.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Conversation (agent session) id the panel is on.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<startup|resume|clear|compact>\",\n \"required\": false,\n \"description\": \"What started the conversation.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id; defaults to PANE_PANEL_ID inside a Pane terminal.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id, with --panel; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels conversation --conversation <id> --source resume --json\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"notes\": [\n \"`sure` in the result is true when the report came since the panel's agent started; a Switch to chat or restart opens a sure conversation, and otherwise opens the folder's most recent one.\",\n \"`--hook` reads the hook's stdin, prints nothing and always exits 0; it is not offered over MCP.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent tab (reviewer, auditor, QA, fix or helper) inside an existing Pane, sharing its worktree. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: use this, not panes create, for any later agent on existing work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. This is the default way to start any later agent on work that already has a Pane, including reviewers, follow-up reviewers, auditors, fix implementers, QA and clean-context helpers. The new tab is a fresh agent that shares the Pane's worktree and branch; it does not create a new worktree. Review, audit and QA tabs never run `git checkout`, `reset` or `stash`, and a fix tab starts only after the previous writer has stopped. Use `panes create` only when the work's branch has no Pane yet. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--chat\",\n \"required\": false,\n \"description\": \"Open --agent claude or codex as a chat tab (ACP, no terminal) instead of a terminal tab. Needs chat tabs on in Settings > Advanced.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"required\": false,\n \"description\": \"Start this Agent Farm profile in a chat tab; Agent Farm prints its ACP launch (agent-farm run --print-acp).\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"required\": false,\n \"description\": \"A launch argument for --agent-farm; repeatable.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command \\\"agent-farm run free-range\\\" --agent claude --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"`--chat` or `--agent-farm` creates a chat tab: --initial-input is its first prompt, sent once when the conversation starts; --wait-ready waits for the agent to start. `panels submit`, `wait`, `screen`, `output`, `last-message`, `report` and `resume` work on chat tabs; `panels input` and `submit-composer` do not.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels open\": {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"details\": \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open the tab in; defaults to PANE_SESSION_ID (the calling panel's Pane, including a Session orchestrator's).\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"http(s) or file URL to open in a browser tab. WSL file URLs use the same automatic path conversion as --file. Pass exactly one of --url or --file.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"File inside the Pane worktree, relative or absolute. WSL panes accept Linux, Windows drive, and WSL UNC paths without manual conversion. Files open in an editor tab; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Tab title override.\"\n },\n {\n \"name\": \"--split\",\n \"required\": false,\n \"description\": \"Open in split view beside the primary tab group (default).\"\n },\n {\n \"name\": \"--tab\",\n \"required\": false,\n \"description\": \"Open in the current tab group instead of split view.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Open the tab without selecting it.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Select the tab (default). Never raises or focuses the Pane window.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels open --file plan.html --source agent --yes --json\",\n \"runpane panels open --url http://localhost:3000 --source agent --yes --json\",\n \"runpane panels open --pane <pane-id> --file src/app.ts --tab --source agent --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"notes\": [\n \"Prefer this over opening the system browser when showing the user a page, plan, report, or dev server.\",\n \"Write plans and reports as self-contained HTML files. Use --url with a file URL to render a page in a live browser, or --file to open its editable source; reopening the same target reuses its tab.\",\n \"When the user views Pane from another computer, a file page loads only files in its own folder and the folders inside it. Keep the page's CSS, scripts and images there, put them inline, or serve the folder over HTTP and open its localhost URL. Give file URLs as the host sees them, not as the other computer would.\",\n \"Opening a tab does not steal window focus; use `panes focus` only on explicit user request.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer. Auto uses Tab to queue while Codex is working and Enter when ready; Pane verifies that the text left the composer. A chat tab has no composer to submit: it fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName, verifiedSubmitted and, for Claude and Codex, delivery (`taken`, `queued`, `in-composer` or `unknown`, with `transcript` or `screen` evidence). If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\",\n \"With `--strategy auto`, if the staged text is still visible in the composer after the first attempt, Pane retries once and checks again: Tab for working Codex, Enter otherwise. It never retries an empty composer.\"\n ]\n },\n \"workspace state\": {\n \"name\": \"workspace state\",\n \"summary\": \"Read one snapshot of every Pane and CLI panel.\",\n \"details\": \"Use this instead of spawning one command per pane or panel.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optionally limit the snapshot to one saved repository.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane workspace state --json\"\n ],\n \"notes\": [\n \"The response carries the daemon epoch and current journal generation.\"\n ]\n },\n \"watch\": {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace transitions using the daemon journal.\",\n \"details\": \"`runpane watch --follow` is the monitor for an agent: with no other flags it prints WATCH OK once, then one line per event (reports, ready, blocked, idle, exits, Session membership, and PR conflicts, checks, and merges), batches non-urgent lines, and ends itself with WATCH EXPIRED after 3 hours with nothing happening. Set individual duration flags to override these defaults. `--until-event` is for harnesses that wake an agent when a process exits (Claude Code background Bash, Codex): it prints the first event worth waking for immediately, without follow settling or batching, and exits 0, and its named cursor means nothing is missed between runs. Every default is listed in the contract under defaults.watch; each override flag replaces one value.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--follow\",\n \"required\": false,\n \"description\": \"Keep watching, with the defaults in defaults.watch.follow: WATCH OK once, then one line per event; READY after 3m of quiet, BLOCKED after 30s, other lines batched at most every 10m (REPORT, BLOCKED, PR conflicts, and failed checks arrive at once); IDLE reminders at 10m, 30m, 1h, 3h, then daily; WATCH EXPIRED and exit 0 after 3h with nothing but IDLE.\"\n },\n {\n \"name\": \"--until-event\",\n \"required\": false,\n \"description\": \"Wait for the first event in defaults.watch.untilEvent.kinds (agent.report, agent.blocked, panel.exited, pane.gone), print that one line, and exit 0. Inside a Pane terminal the cursor defaults to until-<panel id>, so a re-run resumes where the last stopped. A RESET and the caller's own panel never end the wait. With --timeout-ms, exit 124 with WATCH TIMEOUT when the deadline passes. Cannot be combined with --follow or --self-test.\"\n },\n {\n \"name\": \"--session\",\n \"required\": false,\n \"value\": \"<id|name>\",\n \"description\": \"Follow every Pane associated with a named Session (id or exact name). The daemon re-reads membership on every read, so a Pane associated after arming is included and a detached or archived Pane drops out without a re-arm. The Session's own orchestrator panels are never reported. Cannot be combined with --pane or --all-managed; an unknown Session fails.\"\n },\n {\n \"name\": \"--pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Limit to a Pane; repeatable. Default all managed panes. Cannot be combined with --session.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane; repeatable.\"\n },\n {\n \"name\": \"--repo\",\n \"required\": false,\n \"value\": \"<selector>\",\n \"description\": \"Limit to a saved repository (active, id, path, or name). Default all repositories.\"\n },\n {\n \"name\": \"--name-contains\",\n \"required\": false,\n \"value\": \"<text>\",\n \"description\": \"Limit by Pane name substring.\"\n },\n {\n \"name\": \"--include-shells\",\n \"required\": false,\n \"description\": \"Include plain shell panels. Default: agent panels only. A report (agent.report) arrives from any panel either way.\"\n },\n {\n \"name\": \"--kinds\",\n \"required\": false,\n \"value\": \"<kind,...>\",\n \"description\": \"Replace the default event kinds: agent.ready, agent.busy, agent.blocked, agent.unknown, agent.idle, pane.created, pane.gone, panel.exited, panel.stopped, panel.paused, panel.unpaused, agent.report, pane.associated, pane.detached, pr.conflicted, pr.checks, pr.merged. Opt-in only, never in a default: agent.report.missing, and Autopilot's item.created, item.answered, session.created, session.archived, orchestrator.turn_ended. Defaults: defaults.watch.follow.kinds under --follow, defaults.watch.untilEvent.kinds under --until-event, every kind for a one-shot read.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"One JSON object per line. Control lines use kinds _ok, _heartbeat, _reconnected, _error, _reset, _dropped, _timeout, and _expired.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"required\": false,\n \"description\": \"Name the cursor (at-least-once delivery): 1-128 letters, numbers, dots, underscores, or hyphens. Default under --follow: session-<uuid> with --session, else this panel (PANE_PANEL_ID, shortened to panel-<sha256 prefix> when longer than 64 characters); under --until-event: until-<panel id>; otherwise anonymous.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"required\": false,\n \"description\": \"Use an explicit generation cursor instead of a name. Cannot be combined with --as or --follow.\"\n },\n {\n \"name\": \"--from\",\n \"required\": false,\n \"value\": \"<now|earliest>\",\n \"description\": \"Starting point for a new named cursor. Default now (silent baseline); earliest replays the journal.\"\n },\n {\n \"name\": \"--self-test\",\n \"required\": false,\n \"description\": \"Check the daemon watch path without moving any cursor, print WATCH OK, and exit 0.\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long a panel must stay quiet before READY. Default 3m. A BUSY, BLOCKED, UNKNOWN, EXIT, or GONE inside the window cancels it. 0 turns it off.\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long before BLOCKED is printed; an in-pane answer inside it cancels the line. Default 30s. 0 turns it off.\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: batch READY, IDLE, NEW, GONE, EXIT, and UNKNOWN lines at most once per interval; REPORT, BLOCKED, pr.conflicted, pr.base_changed, pr.base_deleted, and failed pr.checks arrive at once and carry the held lines. Default 10m. 0 turns batching off.\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: the first IDLE reminder after a READY panel stays idle. Default 10m. Later reminders back off to 30m, 1h, 3h, then daily. 0 turns IDLE off.\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: end the watch with WATCH EXPIRED and exit 0 after this long with no events but IDLE; any other event restarts the clock. Default 3h. 0 never expires.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: print a HEARTBEAT line this often as proof of life. Default off; a monitor that wakes an agent should leave it off.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"required\": false,\n \"value\": \"<duration>\",\n \"description\": \"Per-request wait in milliseconds; 0 reads once without waiting. Default 60000, capped at 120000 (the follow loop re-issues requests). With --until-event it is the deadline for the whole wait (exit 124 with WATCH TIMEOUT). Accepts ms/s/m/h units; bare values are milliseconds. 0/off is a nonblocking read only, and is rejected with --follow or --until-event.\"\n },\n {\n \"name\": \"--limit\",\n \"required\": false,\n \"value\": \"<count>\",\n \"description\": \"Maximum entries per response. Default 256.\"\n },\n {\n \"name\": \"--ack-now\",\n \"required\": false,\n \"description\": \"At-most-once named-cursor delivery (advance before you act). Default at-least-once.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"required\": false,\n \"description\": \"Include up to 120 characters of unsubmitted composer text (typed text only; prompt suggestions and placeholders excluded) in JSON entries. Default off: under --follow, JSON entries carry only heldInputPresent: true.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"required\": false,\n \"description\": \"Disable redacted held-input detection under --follow: no STUCK lines, and no heldInputPresent in JSON.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\",\n \"required\": false\n }\n ],\n \"examples\": [\n \"runpane watch --self-test\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane watch --follow --json --kinds agent.report,agent.blocked\"\n ],\n \"notes\": [\n \"Every default is in the contract under defaults.watch: follow (what --follow does) and untilEvent (what --until-event waits for). An override flag replaces one value; nothing else needs remembering.\",\n \"Arming: wait for WATCH OK before relying on a --follow monitor. It prints once when the watch is armed. Run the monitor under your harness's background monitor (one line = one notification).\",\n \"Ending: WATCH EXPIRED (exit 0) means nothing happened for --expire-after: say so, and arm a new watch with the next dispatch. A non-zero exit or a WATCH ERROR line means it failed: tell the user and re-arm once; if it dies again, file runpane doctor --report. If the message says the cursor was superseded, keep the replacement monitor and leave the old one stopped. Judge a watch by these lines, never by silence.\",\n \"--until-event prints exactly one line: the event (exit 0), WATCH TIMEOUT (exit 124, only with --timeout-ms), or WATCH ERROR (exit 2).\",\n \"STUCK means real unsubmitted composer text. An agent prompt suggestion (for example a grey Try \\\"...\\\" hint) never counts.\",\n \"Cadence state is held per named consumer. Without numeric --since, follow uses an explicit --as or assigns a Session, panel, or follow-<pid> name. With numeric --since it has no name and bypasses cadence; --as cannot be combined with --since. Held lines survive a reconnect of the same consumer; a changed filter re-delivers them under the new filter.\",\n \"Journal loss is surfaced through reset and dropped metadata.\",\n \"The daemon treats omitted idleAfterMs as disabled so older clients never receive agent.idle unexpectedly.\",\n \"Control lines are not journal entries. Lines text and JSON kind: WATCH OK (_ok, once when armed), HEARTBEAT (_heartbeat, only with --heartbeat), WATCH RECONNECTED (_reconnected, after a retried daemon error), WATCH ERROR (_error; exit 2 unless --follow or --until-event retries it), RESET (_reset, the journal restarted or the cursor fell behind; a fresh baseline follows), DROPPED (_dropped, entries lost to journal truncation), WATCH TIMEOUT (_timeout, --until-event deadline; exit 124), and WATCH EXPIRED (_expired, --expire-after passed; exit 0). Under --until-event, only _timeout and _error print.\",\n \"Under --follow, JSON entries for panels holding unsubmitted composer text carry heldInputPresent: true, the JSON equivalent of the STUCK line. Only typed composer text counts, the same reading as panels screen composer.hasUndeliveredText; an agent's dim or grey prompt suggestion or placeholder does not. --no-held-input turns this off.\",\n \"--session <id|name> scopes the watch to a named Session. The daemon resolves the Session once per request and its associated Panes on every read, so sessions associate and sessions detach take effect without a re-arm; archived and detached Panes drop out, and the Session's own orchestrator panels are excluded. Cadence state is keyed by the Session, not its current Panes, so membership changes keep held lines. A detached Pane's held lines are dropped.\",\n \"Under --session, pane.associated (JOINED <pane-name> pane <pane-id> session <session-id>) and pane.detached (LEFT ...) report membership changes; list them in --kinds next to the agent kinds. Other consumers receive them only when --kinds lists them, so older clients never see an unknown kind.\",\n \"PR watching: the daemon batches up to 25 direct GraphQL fingerprints per host every ~2 minutes. Full details are read on change or a 30-minute backstop. Existing Session branch discovery remains; standalone Panes register with pr watch --url <PR URL> [--head <full-sha>]. Immediate failures, required-check-aware success, head/review/comment/closed/merged events and GitHub outage/recovery events share durable baselines and the journal. A head pin prevents stale success, empty/partial checks never pass, and merge status BLOCKED/UNKNOWN conservatively withholds success. Rate limits honor server waits and reserve 10% for interactive use. Eight other errors or ten comment-only updates stop the watch with pr.stopped. See pr watch/unwatch/list.\",\n \"A daemon that predates --session returns no session field; runpane then fails with WATCH ERROR instead of watching every Pane.\",\n \"Replay: after a reset (RESET/_reset), the baseline restates current state. In JSON, those entries carry replay: true (and changedWhileAway: true after an epoch change). A replayed agent.ready is never READY: re-read runpane sessions overview instead of acting on it. Lines mode prints only CHANGED for them.\",\n \"agent.report (REPORT <pane-name> pane <pane-id> panel <panel-id> <state> [pr#<n>] [<head>][: <question>]) is a worker's `runpane report`. It is in the default kinds of --follow and --until-event, and arrives from any panel: a shell tab or a wrapper launch whose agent is not yet detected included. It bypasses --min-interval, and orchestrators treat it as the completion signal; a READY without a report means look, and maybe nudge.\",\n \"Never pipe --follow into grep -m1, head, or any other exit-on-match reader: it does not exit when its reader does until its next write. To wait for one event, use --until-event.\",\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"--follow has one default cadence: READY 3m, BLOCKED 30s, non-urgent batches 10m, IDLE after 10m with backoff, expiry 3h, heartbeats off. Individual duration flags override it. --present and --profile were removed; use --ready-settle, --blocked-settle, and --min-interval for timing overrides.\",\n \"--until-event delivers matching events immediately without follow settling or batching. A harness that consumes a named follow stream gets its cadence; a process-exit waiter keeps immediate single-event semantics. --follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"Autopilot's kinds reach only watches that list them in --kinds (a Session scope alone does not): item.created and item.answered when an approval or question starts or stops waiting on the person (ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>; JSON itemId and reason), session.created and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>; paneId is the Session's own Pane), and orchestrator.turn_ended when a Session orchestrator goes from working to idle (TURN ENDED ...). Autopilot waits with `runpane watch --as autopilot --kinds item.created,item.answered,session.created,session.archived,orchestrator.turn_ended --until-event --json`.\"\n ]\n },\n \"panes git-status\": {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"details\": \"Use this to check whether a Pane has work to commit, push, or rebase before archiving it or opening a PR.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes git-status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes commit\": {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"details\": \"Use this to commit a Pane's work the way the app's commit action does. The message needs a title line.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes push\": {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"details\": \"Use this to publish a Pane's commits, the way the app's push action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes push --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes pull\": {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"details\": \"Use this to bring remote commits into a Pane, the way the app's pull action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pull --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes rebase-main\": {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"details\": \"Use this to bring main's changes into a Pane. It rewrites the Pane branch history; conflicts are reported, not resolved.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rebase-main --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes restore\": {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"details\": \"Use this to undo `panes archive`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes restore --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes squash-rebase\": {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"details\": \"Use this to land a Pane's work on the repository's main branch the way the app's squash-and-rebase action does. It rewrites the Pane branch and updates main.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash\": {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"details\": \"Use this to set aside work in progress, the way the app's stash action does. `panes stash-pop` brings it back.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash-pop\": {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"details\": \"Use this to bring back work set aside with `panes stash`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash-pop --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes soft-reset\": {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"details\": \"Use this to reword or regroup the latest commit, the way the app's soft reset action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes soft-reset --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes fetch\": {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"details\": \"Use this to refresh remote branches before checking git status or rebasing.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes fetch --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes run-script\": {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"details\": \"Use this to start the dev server or app for a Pane, the way the app's Run button does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes run-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes stop-script\": {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"details\": \"Use this to stop what `panes run-script` started.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stop-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes move\": {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"details\": \"Use this to organize Panes. Folder ids come from `runpane folders list`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<folder-id>\",\n \"required\": true,\n \"description\": \"Folder id from folders list.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"folders list\": {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"details\": \"Use this to find folder ids for `panes move`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders list --repo <repo-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"folders create\": {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"details\": \"Use this to add a folder for grouping Panes.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Folder name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"links create\": {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"details\": \"Use this to hand the user a clickable link. It only builds the URL; opening it navigates and never changes Pane state.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel inside the Pane to select.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": false,\n \"description\": \"Repository id from repos list.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<session-id>\",\n \"required\": false,\n \"description\": \"Session id or name from sessions list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane links create --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"notes\": [\n \"Links look like `pane://open?pane=<pane-id>&panel=<panel-id>`. Give exactly one of --pane, --repo, or --session.\"\n ]\n },\n \"links open\": {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"details\": \"Use this to navigate the app to a Pane, panel, repository, or Session. It raises the Pane window, so run it only when the user asked.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--url\",\n \"value\": \"<pane-url>\",\n \"required\": true,\n \"description\": \"A pane://open link from links create.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane links open --url \\\"pane://open?pane=<pane-id>\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Opening a link never changes Pane state; it selects what the link names.\"\n ]\n },\n \"docs search\": {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"details\": \"Use this before guessing how Pane works. Results are short excerpts with a path; read the full text with `runpane docs read`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"Words to search for.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum results; defaults to 5.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs search --query \\\"archive a pane\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"notes\": [\n \"Works offline. Skills are found in the Pane data directory when the Pane app has installed them.\"\n ]\n },\n \"docs read\": {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"details\": \"Use this with a path from `runpane docs search`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Path from docs search, such as docs/PANE_MCP.md.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs read --doc docs/PANE_MCP.md --json\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"notes\": [\n \"Paths come from docs search: `docs/<file>.md`, `skills/<name>/SKILL.md`, `help/<topic>`, or `command/<name>`.\"\n ]\n },\n \"agents start\": {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. For work that already has a Pane, pass --pane <id>: the agent opens as a new tab sharing that Pane's worktree and branch. Only when the work's branch has no Pane yet, pass --repo and --name to create one. It returns the pane and panel ids and a pane:// link to hand the user.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Existing Pane to add the agent to as a new tab. Use this for any work that already has a Pane.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Name for the new Pane. With --repo, only for work whose branch has no Pane yet.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<task>\",\n \"required\": true,\n \"description\": \"The task to send once the agent is ready.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch to start the worktree from.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"notes\": [\n \"Creates the Pane in the background (no focus change), pinned, and waits for the agent to be ready before sending the prompt.\",\n \"Check on it with `runpane agents status`; send follow-ups with `runpane agents send`.\"\n ]\n },\n \"agents status\": {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"details\": \"Use this to see how an agent started with `agents start` is doing, or whether it needs input.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Screen lines to return; defaults to 40.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"notes\": [\n \"`status` is `working`, `ready`, `blocked`, `idle`, `exited`, or `unknown`. `blocked` means the agent is waiting on a person.\"\n ]\n },\n \"agents send\": {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"details\": \"Use this to give an agent its next instruction. To answer a worker that still owes a report, add `--no-expect-report`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"The message to submit.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report, and Pane keeps waiting for that report.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane agents send --pane <pane-id> --text \\\"$(cat review-notes.md)\\\" --as-file-pointer --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"notes\": [\n \"`delivered` is true when the agent took the message or queued it for after its current turn; `delivery` says which (`taken` or `queued`) and how Pane knows (`transcript` or `screen`). Never resend a delivered message.\",\n \"It types the text, then queues with Tab when Codex is working or submits with Enter when ready. It is for messages, not keys. To answer a menu, use `runpane panels input --panel <panel-id> --keys down,enter --yes`, then check the screen with `runpane agents status`.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\"\n ]\n },\n \"report\": {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"details\": \"Run this as a delegated worker when you finish, fail, or get blocked, as the last step of your task. The orchestrator receives it as an agent.report watch event and treats it as the completion signal. It prints whether anyone was waiting for it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"required\": true,\n \"description\": \"ready: work is ready for review; done: finished with nothing to review; blocked: needs an answer; failed: could not finish.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"required\": false,\n \"description\": \"Pull request number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"required\": false,\n \"description\": \"Head commit the report is about, 7-40 hex characters.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the summary from a file or stdin; the absolute path is kept as summaryPath.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What you need answered; required with --state blocked.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id when not running inside a Pane terminal (PANE_SESSION_ID).\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id when not running inside a Pane terminal (PANE_PANEL_ID).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane report --state blocked --question \\\"Should the migration drop the old column?\\\"\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"notes\": [\n \"Inside a Pane terminal the report is for the current panel (PANE_SESSION_ID and PANE_PANEL_ID). Explicit --panel (with an optional --pane) wins; with neither, the command fails with a clear error.\",\n \"Each report replaces the panel's previous one and survives a Pane restart.\",\n \"agent.report is in the default kinds of `runpane watch --follow` and `--until-event`, and arrives from any panel, a shell tab included. REPORT lines skip the --min-interval batch.\",\n \"The output says who received it: \\\"Delivered to N waiting watcher(s).\\\" counts watches waiting at that moment (liveWatchers in JSON). With no Session and nobody waiting it adds that the report is stored on the panel (runpane agents status --panel <id>) and that a named-cursor watch (runpane watch --as <name> --kinds agent.report) still receives it when it next waits. Zero is not a failure; the exit code is 0 either way.\",\n \"With --state blocked and --question, the person sees an answerable card at once; it rings once the orchestrator relays it or after 10 minutes. The answer reaches this tab as `answer: Q-n → <answer> (by you)`. `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too.\"\n ]\n },\n \"sessions list\": {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"details\": \"List durable named orchestration Sessions.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"notes\": []\n },\n \"sessions create\": {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"details\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Create-time isPinned requires an updated Pane app. Older apps may ignore the field; use sessions pin after creation and read back the result.\",\n \"runtime appears only on WSL Sessions (\\\"wsl\\\", with wslDistribution, Windows only). A Session on the native host has no runtime field on any OS; \\\"windows\\\" is accepted on input as a legacy name for the native host and is not stored.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"Example: {\\\"name\\\": \\\"Release QA\\\", \\\"goal\\\": \\\"Ship 2.5\\\", \\\"decisions\\\": [\\\"Cut from main\\\"], \\\"blockers\\\": [], \\\"outputs\\\": [{\\\"label\\\": \\\"Release PR\\\", \\\"url\\\": \\\"https://github.com/greenfield-inc/Pane/pull/1\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"pull-request\\\"}]}\"\n ]\n },\n \"sessions get\": {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"details\": \"Read one durable named orchestration Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions update\": {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"details\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"archived: true also archives every Pane associated with the Session, as `panes archive --pane` would. A Pane that check refuses stays open (agent-busy, unsent-input, uncommitted-changes, unpushed-commits, uncommitted-and-unpushed, status-unknown), and so does one whose archive fails. The result's `panes` lists `archivedPaneIds`, `keptPanes` (with `code`) and `failedPanes` (with `error`); check a kept Pane with `panes archive --pane <id> --dry-run --json`. Undo is the person's: `panes restore`.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"update only: report is {\\\"summary\\\": non-empty string, \\\"status\\\": reported|verified, \\\"evidence\\\": [at least one link], \\\"reportedAt\\\": ISO 8601 date, \\\"provenance\\\": non-empty string}; report null clears it.\",\n \"Example: {\\\"expectedRevision\\\": 4, \\\"nextAction\\\": \\\"Merge after CI\\\", \\\"decisions\\\": [\\\"Cut from main\\\", \\\"Skip beta\\\"], \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"evidence\\\", \\\"provenance\\\": \\\"qa agent\\\"}], \\\"report\\\": {\\\"summary\\\": \\\"QA passed\\\", \\\"status\\\": \\\"verified\\\", \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\"}], \\\"reportedAt\\\": \\\"2026-10-14T09:31:00Z\\\", \\\"provenance\\\": \\\"qa agent\\\"}}\"\n ]\n },\n \"sessions set-agent\": {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"details\": \"Switch the durable terminal agent for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions associate\": {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"details\": \"Associate a user-visible Pane with a named Session. `panes create` and `panes adopt` already do this automatically when run inside a Session orchestrator; use this for existing Panes or after `--no-associate`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions detach\": {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"details\": \"Detach a Pane from a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions overview\": {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"details\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"notes\": [\n \"The result includes `locks`: named locks scoped to the Session or held by its Panes (see `runpane lock`).\",\n \"Each `panes[].panels[]` entry has `type`, the tab's panel type (`terminal`, `browser`, `editor`, `explorer`, `diff`, `logs`, `notes`, `dashboard` or `setup-tasks`). A Pane linked whole lists every tab, with `explorer` and `diff` last; a link that names tabs lists exactly those. Only terminals carry an agent `state`; other tabs report `unknown` and do not change the Session's `status`. A missing panel has no `type`, and daemons that predate it omit it.\",\n \"Each `panes[].panels[]` entry has `report`, that panel's latest `runpane report` (state, `pr`, `head`, `question`, `summary`, `summaryTruncated`, `summaryPath`, `reportedAt`). When the summary is a complete JSON object, `report.data` holds it exactly as the worker gave it; Pane never reads or renames its fields. A plain-text, array or scalar summary, invalid JSON, JSON nested more than 64 levels deep, or a truncated summary (`summaryTruncated`) has no `data`. A panel that never reported has no `report` but keeps `state` and `lastActivityAt`. `panes[].report` is still the newest report among the Pane's panels, with its `panelId`; daemons that predate per-panel reports omit `panels[].report`.\"\n ]\n },\n \"lock acquire\": {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"details\": \"Run this before using a resource that only one agent may use at a time, such as a shared test account, and release it when done. The owner is the calling Pane and panel ($PANE_SESSION_ID and $PANE_PANEL_ID, or --pane/--panel). The lock is scoped to the owner's Session when its Pane belongs to one, so two Sessions can each hold `testing-account`; otherwise it is global. It succeeds when the lock is free, expired, or already yours (which renews the TTL). Otherwise it returns ok:false with heldBy and expiresAt at once, or with --wait blocks in the daemon until the lock is released, expires, or its owner's panel exits or Pane is archived.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"required\": true,\n \"description\": \"How long the lock is held before it expires, such as 90s, 30m, or 2h; at most 24h. Acquire again before it runs out to renew.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Wait this long for the lock to come free. The CLI waits in daemon calls of up to 120 seconds each.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What the lock is for, shown to other agents. Required outside a Pane terminal, where it also names the owner.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock acquire --name staging-db --ttl 10m --json\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"notes\": [\n \"Exit status is 0 only when the lock is yours; ok:false (exit 1) means another owner holds it, named by heldBy until expiresAt.\",\n \"Release the lock as soon as you are done with the resource: `runpane lock release --name <name>`. If you interrupt a waiting acquire, the lock can still be granted to you for up to two minutes; release it or let the TTL expire.\",\n \"Pane releases the lock when its TTL runs out, when the owner panel exits, or when the owner Pane is archived. Quitting Pane does not release it; locks survive a restart.\"\n ]\n },\n \"lock release\": {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"details\": \"Only the owner can release a lock. --force releases another owner's lock, for example a stuck worker's; use --session to name a Session-scoped lock from outside that Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Release the lock even though another owner holds it.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session whose lock to release; defaults to the caller's Session, or global.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Outside a Pane terminal, the note the lock was acquired with.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock release --name testing-account --json\",\n \"runpane lock release --name testing-account --session \\\"<session-id-or-name>\\\" --force --json\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"notes\": [\n \"released:false with ok:true means no such lock was held. ok:false with reason not-owner (exit 1) means another owner holds it.\"\n ]\n },\n \"lock list\": {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"details\": \"List every held lock, or with --session the locks scoped to that Session plus those its Panes hold. `runpane sessions overview` includes the same Session locks.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock list --json\",\n \"runpane lock list --session \\\"<session-id-or-name>\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"notes\": []\n },\n \"workspace list\": {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"details\": \"Shows this machine's workspace state, then each other Mac, Windows, and Linux machine on your tailnet with its OS, online state, and shell. Your own machines come first; other people's machines carry their owner's login and answer only when their owner lets you in.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace list --json\"\n ],\n \"notes\": [\n \"A machine has joined when Pane runs there with workspaces on; it answers on https://<machine>.<tailnet>.ts.net:8443.\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n \"workspace enable\": {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"details\": \"Serves this Pane's daemon with tailscale serve on port 8443 in HTTP proxy mode. Only your own Tailscale login is accepted unless you set Who can connect to Everyone on tailnet; nothing is opened to the internet.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace enable\"\n ],\n \"notes\": [\n \"Workspaces are on by default when Tailscale is installed and signed in; this undoes runpane workspace disable.\"\n ]\n },\n \"workspace disable\": {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"details\": \"Turns workspaces off for this machine until runpane workspace enable. The remote daemon on port 443 and pairing codes are unaffected.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace disable\"\n ],\n \"notes\": [\n \"Other machines can no longer reach this one; this machine can still reach theirs.\"\n ]\n },\n \"workspace read\": {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"details\": \"Reads the same way on every OS. On Windows, /mnt/c/... reaches C:\\\\... and WSL paths such as /home/<user>/... are read through \\\\\\\\wsl.localhost\\\\<distro>\\\\. Directories list their entries.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form; ~ is that machine's home.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox read 'C:\\\\Users\\\\me\\\\.pane\\\\plans\\\\topic\\\\index.html'\",\n \"runpane workspace read /mnt/c/Users/me/notes.md\"\n ],\n \"notes\": [\n \"--json returns content with encoding utf8 or base64.\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n \"workspace write\": {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"details\": \"Takes the same path forms as workspace read. The content replaces the file.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text that replaces the file; an empty string empties it. Without it, the CLI writes stdin. The MCP tool requires it.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"echo done | runpane workspace parsa-devbox write '~/notes/status.md'\"\n ],\n \"notes\": [\n \"Binary stdin is sent as base64 and written byte for byte.\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n \"workspace exec\": {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"details\": \"Runs in the shell Pane uses for terminals on that machine (PowerShell or Git Bash on Windows, the login shell elsewhere), with that Pane's runpane first on PATH. The exit code becomes runpane's exit code. Without --, the command starts at the first word that is not an exec option. Without <machine>, a --cwd that cannot exist here picks the machine.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to let a --cwd that cannot exist here pick the one joined machine it fits.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Working directory on that machine, in any path form; defaults to its home.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Stop the command after this long (default 120000, at most 540000).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command line, run by that machine's shell.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox exec --json -- git -C C:\\\\src\\\\app status --short\"\n ],\n \"notes\": [\n \"This is SSH-level power: a local agent's sandbox and permission prompts see only runpane workspace exec, not what runs on the other machine.\",\n \"Pass the command as one quoted string; several words after -- are joined with spaces, so their own quoting is lost.\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n \"handoff\": {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"details\": \"Two steps. The agent writes the note, because only it has the context: run `runpane handoff --template > ~/handoff.md` (outside the checkout) and fill in every section (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state), writing \\\"None\\\" where one does not apply. Then `runpane handoff \\\"<destination>\\\" --note-file ~/handoff.md` moves it: it rejects a note with a missing or empty section, refuses an unpushed branch unless --push, finds the destination's saved repository with the same remote, fetches the branch there, writes the note to `~/.pane/handoffs/` on that machine with its origin in the front matter and receiver instructions appended, and starts the agent in a new Pane branched from your branch. When you run in a Pane tab and the receiver is your own agent, it carries your session instead of starting fresh: another machine gets a copy of the session's files in its agent store and resumes the same id; this machine resumes a fork. The receiver starts with no prompt, and the CLI checks it resumed and shows the session's last message, then tells it where the session moved. The receiver verifies the head commit, continues from Next steps, pushes to your branch, and reports back to your panel with `runpane workspace <your machine> panels submit`. Run from a Session's terminal (its folder is outside git), handoff moves the Session instead: it skips git, writes the note to the other machine the same way, and creates a Session there with the same name, goal and agent (numbered \\\"<name> 2\\\" if the name is taken), whose context and next action point at the note; a same-agent Session also gets your conversation's files and resumes it when first opened. The sender Session stays as it is; the new one waits for your first message. A Session cannot be handed off to the machine it is on.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<destination>\",\n \"required\": false,\n \"description\": \"Freeform text naming the agent (claude, codex, cursor), and optionally the model, the effort, and the machine: a Tailscale machine name or unique prefix (list them with `runpane workspace list`), or \\\"this machine\\\". Required unless --agent is given. Examples: \\\"codex gpt-5 high on parsas-macbook-pro\\\", \\\"claude opus on parsa-devbox\\\", \\\"cursor on this machine\\\".\"\n },\n {\n \"name\": \"--template\",\n \"required\": false,\n \"description\": \"Print the note template, with this checkout's (or Session's) origin and git state in its front matter, and exit.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"The note to check and send; required unless --template or --dry-run. Without it, --dry-run checks only the destination and git state.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Destination machine; overrides the text.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Receiving agent; overrides the text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"required\": false,\n \"description\": \"Model for the receiving agent, such as opus, sonnet, gpt-5, or claude-opus-5-5; overrides the text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"required\": false,\n \"description\": \"minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Saved repository on the destination. Defaults to the one with a remote for this repository.\"\n },\n {\n \"name\": \"--push\",\n \"required\": false,\n \"description\": \"Commit uncommitted work (except the note) as WIP and push the branch before sending; never forces. Without it, an unpushed branch stops the handoff.\"\n },\n {\n \"name\": \"--note-only\",\n \"required\": false,\n \"description\": \"Start a fresh agent with the note instead of carrying your own session. Without it, a handoff to the same agent carries and resumes your session.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check destination daemon reachability and resolve its saved repository name, path, environment and OS; print the note check and git state without committing, pushing, fetching, writing notes or creating a Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane handoff --template > ~/handoff.md\",\n \"runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \"runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run --json\",\n \"runpane handoff --machine build-server --agent cursor --note-file ~/handoff.md\"\n ],\n \"notes\": [\n \"Another machine is reached through runpane workspace (Tailscale); it needs Pane running there with workspaces enabled and the repository saved. Repository listing and Pane creation call the reached daemon directly; no global destination CLI installation is required.\",\n \"Destination preflight runs before sender commit/push. Inside WSL, an absent or refused local Linux daemon can fall back to its Windows host through reachable Windows Tailscale and enabled workspaces; explicit Pane directory selection keeps its instance. Select a native Windows saved repository; WSL saved repositories are rejected before sending.\",\n \"A successful handoff requires initialInput.verifiedSubmitted and delivery taken or queued without blocked/error evidence. Unverified delivery exits nonzero with the created Pane, panel and a panels screen inspection command; inspect before retrying.\",\n \"From a Session's terminal (PANE_ORCHESTRATION_SESSION_ID set, folder outside git) the receiver is a Session, not a Pane: it needs only runpane:sessions:create and runpane:machine:write on the destination, pushes nothing, and refuses this machine before sending. JSON output then has receiver.route \\\"session\\\", session {id, name, machine} and no git field; a dry run reports the name the Session would get.\",\n \"Write the note for a reader with no context: quote errors and commands exactly, and record approaches that failed so they are not retried. Leave the front matter to the CLI.\",\n \"Model and effort become agent flags: claude --model/--effort, codex -m and -c model_reasoning_effort, cursor-agent --model. Cursor effort is not supported and is rejected before side effects.\",\n \"Run it from the checkout being handed off. On another machine the receiver works in a new Pane; on this machine, from the Pane that owns this checkout, it works in a new tab of that Pane (the result's receiver.route and receiver.reason say which). Either way it pushes back to your branch, so stop changing that branch yourself.\",\n \"JSON continuity: { carried, result: continued | inconclusive | not-continued | fresh-agent, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }; a dry run sets carried, sessionId, fork, files and bytes and no result. inconclusive exits 0 and next says to ask the receiver \\\"What were you last working on? Answer in one or two sentences.\\\"; not-continued exits 1, keeps the receiver and gives the --note-only command for the person to choose. After continued or inconclusive, the sender ends its turn and makes no more changes on the branch. From a Session the result is inconclusive until the new Session is opened. A Cursor session is carried only to another machine (best effort); on this machine the result is not-continued, since a copied Cursor chat still writes to the original. The destination's agent store is assumed at its default location (~/.claude, ~/.codex, ~/.cursor); a copy already there that holds history the sender's lacks, or that an agent there is running, is never overwritten (not-continued). checks.originalAppended is set when the running sender's own turn appended to its transcript during the fork; every byte it held before is unchanged. A destination Pane too old to resume a carried session is refused before anything is sent (ERR_HANDOFF_RESUME_UNSUPPORTED).\"\n ]\n },\n \"sessions unpin\": {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pin\": {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pause\": {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"details\": \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived; a chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`, until `sessions resume` resumes them. A Session reads as paused (`sessions get` `session.paused`) while its chat and all of its Panes are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pause --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"sessions resume\": {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"details\": \"Resumes the paused tabs of a Session's chat and Panes. An archived Session is refused. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions resume --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"pr watch\": {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"details\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<full-sha>\",\n \"required\": false,\n \"description\": \"Pin success to this 40-character SHA; otherwise pin the first observed head.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr unwatch\": {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"details\": \"Remove a Pane’s explicit PR registration.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr list\": {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"details\": \"List PR registrations and stopped watches.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"inbox\": {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"details\": \"An orchestrator runs it when it starts or resumes, to catch reports, missing reports, questions and answers that arrived while it was down. Pane never adds the inbox to the orchestrator's turns.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session; defaults to PANE_ORCHESTRATION_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox --session \\\"$PANE_ORCHESTRATION_SESSION_ID\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"notes\": [\n \"Items: report (agent.report), report.missing (agent.report.missing), and blocked (agent.blocked, with the prompt's screen lines). Reports coalesce per panel, blockers clear when the panel moves on, and each Session keeps at most 200 items until acknowledged or superseded. A journal replay watermark recovers committed outcomes after restart within journal retention.\",\n \"A newer report replaces the panel's older report or missing report.\",\n \"`answers` lists what the Session's workers were told, kept 7 days. One with no `decision` still needs recording: `runpane inbox ack <id> --decision <D-n>`. An older Pane leaves `answers` out.\"\n ]\n },\n \"inbox ack\": {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"details\": \"Acknowledge each item once you have acted on it, so the hook stops repeating it. With --decision, also record the D-n of an answer the worker already got.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<item-id>...\",\n \"required\": false,\n \"description\": \"Item ids from `runpane inbox`.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"required\": false,\n \"description\": \"Record this D-n on each named item, such as D-147. Not with --all.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Only clear items of this Session; required with --all.\"\n },\n {\n \"name\": \"--all\",\n \"required\": false,\n \"description\": \"Clear every item of the Session.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox ack in-1a2b3c4d --json\",\n \"runpane inbox ack in-1a2b3c4d --decision D-147 --json\"\n ],\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"notes\": [\n \"Unknown ids are reported under `unknown`; acknowledging twice is harmless.\",\n \"An id already cleared whose answer has no decision still records it.\"\n ]\n },\n \"loops list\": {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"details\": \"Lists every loop: its id, target (projectId for a repo, or orchestrationSessionId for a Session), cron and scheduleText, enabled, nextRunAtMs (null while paused), and its newest runs. A run's sessionId is its Pane (null for a Session loop's runs); read its progress with `runpane agents status --pane <id>`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops list --json\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"notes\": [\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops create\": {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"With --repo, each run is a new Pane in a fresh worktree branched from the repo's default base branch, started like `runpane panes create` with the agent or command and prompt given here. With --session, each run sends the prompt to that Session's agent instead of creating a Pane; a Session loop needs --agent and --prompt and takes no --tool-command. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": true,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--paused\",\n \"required\": false,\n \"description\": \"Create the loop paused.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops create --repo <repo> --name dependency-check --cron \\\"0 9 * * 1\\\" --tool-command \\\"pnpm test\\\" --yes --json\",\n \"runpane loops create --session new --name morning-triage --cron \\\"0 9 * * 1-5\\\" --agent claude --prompt \\\"Triage the new issues\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": [\n \"Saving rejects a cron whose runs fall less than 60 minutes apart. Check one first with --dry-run.\"\n ]\n },\n \"loops update\": {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"Reads the loop, replaces the values you pass, and saves it. Passing --agent clears --tool-command and the other way round; --repo and --session replace each other the same way. Pause and resume with `loops pause` and `loops resume`. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": false,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops update --loop <loop-id> --cron \\\"0 4 * * *\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": []\n },\n \"loops run\": {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"details\": \"Starts a run now, as a new Pane or a message to the loop's Session, even while the loop is paused or another run is still going. The schedule stays the same, and the run counts toward the 10 kept runs.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops run --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": [\n \"The result is the loop; `runs[0]` is the new run, with `status` started or failed and `sessionId` its Pane (null for a Session loop).\",\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops pause\": {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"details\": \"A paused loop starts no scheduled runs. `loops run` still starts one by hand.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops pause --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops resume\": {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"details\": \"Runs resume at the next scheduled time. Runs missed while paused are not caught up.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops resume --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops delete\": {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"details\": \"Deletes the loop and its schedule.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops delete --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"notes\": []\n },\n \"notify\": {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"details\": \"Prefer this to asking in the terminal whenever you need the person: to pick between options (ui options), to approve or deny an action (ui approve), to type a short answer (ui ask), or to tell them something that needs no answer (ui info). Pane shows it on screen even while the person works in another app, and the call waits until they answer, however long that takes. info returns at once.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"required\": false,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer. info: an update that returns at once. Required unless resume, took or update.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"info\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The question or update in one line, about 80 characters; long titles wrap to 3 lines. Required unless resume, took or update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Why it matters, shown under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there unless questions: 1 to 9 choices. The answer is the chosen label. Say what each does in detail, and mark the one you recommend. With update, a reworded choice adds was, its old label.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only: a preformatted block, such as the command or SQL to approve, behind a disclosure.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only, with detail: the disclosure's label, such as \\\"Show the SQL\\\"; up to 40 characters. Defaults to \\\"Show details\\\".\",\n \"schema\": {\n \"type\": \"string\",\n \"maxLength\": 40\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui info only: one button that opens a URL.\",\n \"schema\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files, pages or approvals, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https. Another approval is {label, approval}, its id or Q-n.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"required\": false,\n \"description\": \"A Session orchestrator relaying a worker's question: the waiting item's id from `runpane inbox`. The same card gains these choices, context and links instead of a second card showing. ui and title are still required.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Autopilot's own item: the Session it is about, shown as its connected Session. Without it, the item links to Autopilot's chat.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, instead of choices: 2 to 4 questions asked at once, each with 1 to 9 choices and one pick. The result's answers holds one label per question; answer joins them with \\\" · \\\".\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n }\n }\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After a reply result or a `reply:` line: the approval's id or Q-n, with tookAs answer (record the reply as the answer) or question (it asked something; find out, then call update). Pass no question fields.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"required\": false,\n \"description\": \"With took: answer or question. On the command line it can follow the id instead (`--took Q-20 answer`).\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n }\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With took question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After you took a reply as a question and found out: the approval's id or Q-n. Saves a new version with any of title, context, choices, questions and links you pass (the rest stay), shows it as updated, and waits on it like any notify call. Needs asked and whatChanged; takes no ui.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: what changed, one line per change.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"The id from a waiting result: keep waiting on that question. Pass no other fields.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane notify --ui options --title \\\"Which database for the cache?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]' --json\",\n \"runpane notify --ui approve --title \\\"Drop the legacy_users table?\\\" --detail \\\"DROP TABLE legacy_users;\\\" --detail-label \\\"Show the SQL\\\" --json\",\n \"runpane notify --ui ask --title \\\"Which branch should I base this on?\\\" --suggestions '[\\\"main\\\",\\\"release\\\"]' --json\",\n \"runpane notify --ui info --title \\\"PR #812 is ready for review\\\" --action '{\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}' --json\",\n \"runpane notify --ui approve --title \\\"Ship the release notes?\\\" --links '[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR #812\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}]' --json\",\n \"runpane notify --relays in-1a2b3c4d --ui options --title \\\"Q-39: Run the script?\\\" --choices '[{\\\"label\\\":\\\"Run it\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]' --json\",\n \"runpane notify --ui options --title \\\"Ship 2.5 today: which parts go out?\\\" --questions '[{\\\"title\\\":\\\"Release notes\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Ship as written\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]},{\\\"title\\\":\\\"Intel Mac build\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Include it\\\"},{\\\"label\\\":\\\"Skip it\\\"}]}]' --json\",\n \"runpane notify --took Q-20 answer --json\",\n \"runpane notify --took Q-20 question --status \\\"Checking whether it can be undone\\\" --json\",\n \"runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: Settings > Search turns it off again\\\" --context \\\"Search takes 4 s; the index brings it under 100 ms. It can be undone from Settings.\\\" --json\",\n \"runpane notify --resume n-4f2a --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"Write the card the way the pane MCP server's instructions say: in plain words, what the work is for, what just happened, and what you need.\",\n \"Results: options returns {answer: <chosen label>, note?}, with questions also answers (one label per question); approve returns {approved: true|false, note?}; ask returns {answer}; info returns {delivered: \\\"screen\\\"|\\\"inbox\\\"}.\",\n \"A result of {reply, picks?, id, replyId, next} means the person typed a reply instead of only picking: it may answer the question or ask one. Read it, then call `runpane notify --took <replyId> answer` to record it as the answer, or `runpane notify --took <replyId> question` and find out, then `runpane notify --update <replyId> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with whatever changed, instead of asking again. With no open call the reply reaches your tab as a `reply:` line; `correction:` lines fix how it was taken. --took returns {took, id, next?}. Use its replyId rather than the Q-n: only this call was given it, so it works from outside a Pane tab too.\",\n \"Only the reply's reader may run took and update: the asker, the orchestrator that relayed it, or Autopilot. Anyone else gets ERR_NOTIFY_NOT_READER.\",\n \"A result of {waiting: true, id} means the question is still on screen and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting. After 15 minutes on this loop Pane gives up and returns unavailable.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now (Pane is not running, Notify is off, or the wait ended): ask in the terminal instead. It is a result, not an error.\",\n \"Inside a Pane terminal the question names its Pane and panel (PANE_SESSION_ID and PANE_PANEL_ID); elsewhere it names the working directory.\",\n \"An older Pane ignores links and shows the card without them.\",\n \"With relays the call waits on the worker's card as on any notify, and the answer also goes straight to the worker's tab as `answer: Q-n → <answer> (by you)`. Then record it: `runpane inbox ack <item> --decision <D-n>`.\",\n \"Invalid input, such as choices with ui ask, 10 choices, or ui with took or update, fails with a message naming the field, and nothing is shown.\"\n ]\n },\n \"autopilot status\": {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"details\": \"Autopilot (experimental) is one always-running agent above every Session. It is not a Session: its run lives in <PANE_DIR>/autopilot/. Read this first to know whether it is on, entitled, and running.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot status --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"notes\": [\n \"Works while Autopilot is off (enabled: false), so a caller can tell off from broken.\"\n ]\n },\n \"autopilot waiting\": {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"details\": \"Autopilot's queue, read from each machine's table with no copy: every item waiting on the person on every machine it can reach, filed under its Session (or not in one), this machine's first, with who asked, how long it has waited, whether it is on the stack, and whether it is stale or quiet. `offline` names saved machines it can't reach, and `cantSee` names the person's Tailscale machines not saved here.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot waiting --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"Its ids go straight to `runpane autopilot answer`, `close`, `rewrite` and `stack push --item`, whichever machine holds the item.\",\n \"A `quiet` item is a worker's question its orchestrator still has first call on; a `stale` one has waited 3 days and no longer rings.\",\n \"An older Pane leaves `offline` and `cantSee` out.\"\n ]\n },\n \"autopilot stack\": {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"details\": \"The stack holds entries in the order Autopilot put them; one is shown at a time and the next comes up when the person resolves it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off. A resolved entry carries doneAt.\"\n ]\n },\n \"autopilot stack push\": {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"details\": \"Give exactly one of --item, --file, --url, or --session. A new entry never replaces the one on screen; --next puts it right after it. runpane resolves relative file paths (the entry's and its tabs') against the caller's directory before sending.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"A document to show: HTML loads as a page, anything else as text.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"An http(s) page or running app to show.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"A Session whose overview to show.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The entry's title; defaults to the item's, file's, or Session's own.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Autopilot's one line about why it is here.\"\n },\n {\n \"name\": \"--tab\",\n \"value\": \"<path-or-url>\",\n \"required\": false,\n \"description\": \"An extra tab: a file path, or an http(s) URL. Repeatable on the CLI.\"\n },\n {\n \"name\": \"--next\",\n \"required\": false,\n \"description\": \"Put it right after the entry on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack push --item n-1a2b3c --note \\\"The release orchestrator wants a go\\\" --json\",\n \"runpane autopilot stack push --file docs/plan.html --tab https://localhost:5173 --next --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_PAUSED (the person paused Autopilot; wait for Go on), AUTOPILOT_STACK_INVALID (no such item, file, URL, or Session).\",\n \"The result says where the entry went: position of total, and shown when it is on screen now.\"\n ]\n },\n \"autopilot answer\": {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"details\": \"Give exactly one of --choice, --approve, --deny or --text, and --because. The machine that holds the item delivers the answer at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the person can Override it. Skill behavior, not a setting: answer only when the way forward is clear and the item relates to your work with the person, or when the person told you to handle approvals.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"required\": false,\n \"description\": \"The chosen label of an options item.\"\n },\n {\n \"name\": \"--approve\",\n \"required\": false,\n \"description\": \"Approve an approve item.\"\n },\n {\n \"name\": \"--deny\",\n \"required\": false,\n \"description\": \"Deny an approve item.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A typed answer.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A note for the asker, with --choice, --approve or --deny.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why, recorded on the item: a D-n, a document or the person's words.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot answer n-1a2b3c --choice \\\"Run it\\\" --because \\\"D-22\\\" --json\",\n \"runpane autopilot answer n-1a2b3c --approve --because \\\"The person said to ship it\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot close\": {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"details\": \"The machine that holds the item closes it, tells its asker, and records \\\"Closed by Autopilot: <why>\\\" on every device. The person can reopen it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why it no longer needs the person, recorded on the item.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot close n-1a2b3c --reason \\\"The PR it asks about merged yesterday\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot rewrite\": {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"details\": \"The old item reads \\\"Closed by Autopilot: rewritten as …\\\" and the new one \\\"Rewritten by Autopilot from Q-n\\\". The rewrite stays on the old item's machine, and its answer goes to the original asker. runpane resolves relative --links paths against the caller's directory; a file must exist on the item's machine.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask>\",\n \"required\": true,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"The question in one line.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Background the person can open under Details.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there: 1 to 9 choices. The answer is the chosen label. Mark at most the one you recommend.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files or pages, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot rewrite n-1a2b3c --ui options --title \\\"Q-39: Run the import on the copy first?\\\" --choices '[{\\\"label\\\":\\\"Copy first\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Skip\\\"}]' --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"The result's id is the new item's. Put that one on the stack.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"This repository is used with [Pane](https://runpane.com). Drive it with the CLI or the `pane` MCP server.\",\n \"\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.\",\n \"\",\n \"When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \\\"<question>\\\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.\",\n \"\",\n \"CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.\",\n \"\",\n \"MCP: packaged Pane registers a stdio server named `pane` with Claude Code, Codex, and Cursor. Check the connection with `claude mcp list`, `codex mcp list`, or `agent mcp list`. Cursor may ask you to enable `pane` with `agent mcp enable pane`. If tools are missing, add it in the agent's MCP settings: Claude Code `claude mcp add --scope user pane -- npx --yes runpane@latest mcp`; Codex (`~/.codex/config.toml`) table `[mcp_servers.pane]` with `command = \\\"npx\\\"` and `args = [\\\"--yes\\\", \\\"runpane@latest\\\", \\\"mcp\\\"]`; Cursor (`~/.cursor/mcp.json`) uses `mcpServers.pane` with the same `npx` command and args; any other stdio client uses them too.\"\n ],\n \"notifyGuidance\": {\n \"rules\": [\n \"Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.\",\n \"In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \\\"<question>\\\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.\",\n \"Ask only what you can't find out yourself.\",\n \"Put related questions in one card.\",\n \"Send `info` rarely, only for what the person must know now.\",\n \"Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.\",\n \"A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\\\"was\\\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.\",\n \"For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`.\"\n ],\n \"cardRule\": \"The person is juggling many tasks and has lost the thread. In plain words, say what the work is for (the outcome, not the file), what just happened, and what you need. For a decision, give the options and what each one means. Pane already shows which Pane and agent sent it.\",\n \"results\": \"`waiting` means call `notify` again with `resume`. `unavailable` means ask in the terminal; for a secret, ask the person to save it in Pane under Settings > Secrets.\",\n \"examples\": [\n {\n \"name\": \"Needs a decision\",\n \"ui\": \"approve\",\n \"bad\": [\n {\n \"title\": \"Approve the migration?\",\n \"context\": \"Ready to run 0042_add_index on prod. Proceed?\"\n }\n ],\n \"good\": {\n \"title\": \"Speed up slow search: run the database change?\",\n \"context\": \"Search in the app takes 4 s, and an index brings it under 100 ms. It changes the live database and can't easily be undone. Approve runs it now; Deny keeps it ready for later.\"\n }\n },\n {\n \"name\": \"Blocked\",\n \"ui\": \"options\",\n \"bad\": [\n {\n \"title\": \"Blocked\",\n \"context\": \"CI failing on macos-intel, hdiutil resource busy.\"\n }\n ],\n \"good\": {\n \"title\": \"The 2.5 release is stuck on a GitHub hiccup\",\n \"context\": \"The fixes you asked for pass everywhere except one Mac build machine, which errored. That's GitHub's machine, not our code.\",\n \"choices\": [\n \"Rerun it (about 15 min)\",\n \"Ship without the Intel Mac build\"\n ]\n }\n },\n {\n \"name\": \"Just so you know\",\n \"ui\": \"info\",\n \"bad\": [\n {\n \"title\": \"Done\",\n \"context\": \"Completed refactor of src/notify/queue.ts (head 06d310a).\"\n },\n {\n \"title\": \"PR #812 is ready\",\n \"context\": \"Tests pass.\"\n }\n ],\n \"good\": {\n \"title\": \"Questions from agents now wait until you answer\",\n \"context\": \"The fix you asked for this morning is done and tested. Next is your review: one click to merge.\"\n }\n },\n {\n \"name\": \"A question for ask\",\n \"ui\": \"ask\",\n \"bad\": [\n {\n \"title\": \"Which option?\",\n \"context\": \"1, 2 or 3?\"\n }\n ],\n \"good\": {\n \"title\": \"What should the release headline say?\",\n \"context\": \"You're writing the 2.5 release notes, and the headline is the first line people read. A few words is enough.\"\n }\n }\n ]\n }\n }\n}") +RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \"^22.23.4 || ^24.22.1 || ^26.12.1\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false,\n \"watch\": {\n \"follow\": {\n \"summary\": \"What `runpane watch --follow` does with no other flags: quiet, batched updates and expiry after 3 hours with nothing happening. Individual duration flags override the defaults.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.ready\",\n \"agent.blocked\",\n \"agent.idle\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"pane.gone\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\"\n ],\n \"readySettle\": \"3m\",\n \"blockedSettle\": \"30s\",\n \"minInterval\": \"10m\",\n \"idleAfter\": \"10m\",\n \"idleBackoff\": true,\n \"expireAfter\": \"3h\",\n \"heartbeat\": \"off\",\n \"includeShells\": false,\n \"heldInputDetection\": true\n },\n \"untilEvent\": {\n \"summary\": \"What `runpane watch --until-event` does with no other flags: waits for the first event worth waking an agent for, prints it immediately without follow settling or batching, and exits 0.\",\n \"kinds\": [\n \"agent.report\",\n \"agent.blocked\",\n \"panel.exited\",\n \"pane.gone\"\n ]\n }\n }\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\",\n \"cursor\",\n \"opencode\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n },\n \"cursor\": {\n \"title\": \"Cursor\",\n \"command\": \"cursor-agent --force --trust\",\n \"description\": \"Open a Cursor Agent terminal tab and allow the initial input to drive the agent.\"\n },\n \"opencode\": {\n \"title\": \"OpenCode\",\n \"command\": \"opencode --auto\",\n \"description\": \"Open an OpenCode terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"terminalKeys\": {\n \"enter\": \"\\r\",\n \"escape\": \"\\u001b\",\n \"tab\": \"\\t\",\n \"backspace\": \"\",\n \"space\": \" \",\n \"up\": \"\\u001b[A\",\n \"down\": \"\\u001b[B\",\n \"right\": \"\\u001b[C\",\n \"left\": \"\\u001b[D\",\n \"ctrl-c\": \"\\u0003\",\n \"ctrl-d\": \"\\u0004\"\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true,\n \"localControl\": false\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true,\n \"localControl\": false\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update both Pane and the invoked supported CLI through an independent helper.\",\n \"usage\": [\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\"\n ],\n \"target\": \"client\",\n \"localControl\": false\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version [--pane-dir <path>]\",\n \"runpane --version\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, runpane-on-PATH, and remote setup diagnostics.\",\n \"openWorld\": true,\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"runpane doctor --fix [--json] [--pane-dir <path>]\",\n \"runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"usage\": [\n \"runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"admin\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command <command> [--json]\"\n ],\n \"toolsets\": [\n \"admin\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"localControl\": false\n },\n {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"usage\": [\n \"runpane mcp [--toolsets <name,...>] [--read-only]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path <path> [--name <name>] [--json] [--yes] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"usage\": [\n \"runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"usage\": [\n \"runpane repos update --repo <selector> [--name <name>] [--build-script <script>] [--run-script <script>] [--archive-script <script>] [--worktree-folder <path>] [--open-ide-command <command>] [--system-prompt <text>] [--env <json>] [--env-remove <name,...>] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos rename --repo <selector> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"repos\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"usage\": [\n \"runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"usage\": [\n \"runpane secrets set --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"localControl\": true\n },\n {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"usage\": [\n \"runpane secrets delete --project <project> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"secrets\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"usage\": [\n \"runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... [--pane-dir <path>] -- <command>...\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"usage\": [\n \"runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"usage\": [\n \"runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace state\",\n \"summary\": \"Read one workspace snapshot of every Pane and CLI panel.\",\n \"usage\": [\n \"runpane workspace state [--repo <selector>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceStateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"usage\": [\n \"runpane workspace list [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"usage\": [\n \"runpane workspace enable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"usage\": [\n \"runpane workspace disable [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"usage\": [\n \"runpane workspace [<machine>] read <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"usage\": [\n \"runpane workspace [<machine>] write <path> [--content <text>] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"usage\": [\n \"runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true,\n \"openWorld\": true,\n \"toolsets\": [\n \"core\",\n \"workspace\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"usage\": [\n \"runpane handoff --template\",\n \"runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace agent and Pane transitions using a daemon-held cursor.\",\n \"usage\": [\n \"runpane watch [--follow|--until-event] [--session <id|name>|--pane <id>] [--json] [overrides]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"workspaceWaitRequest\",\n \"workspaceWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"usage\": [\n \"runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--source user|agent] [--focus|--no-focus] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] [options] [--pane-dir <path>]\",\n \"runpane panes create --from-json <path|-> [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane without changing the worktree.\",\n \"usage\": [\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [--resume <id>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\",\n \"runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--folder <name>] [--launch [--prompt <text>|--prompt-file <path|->] [--wait-ready] [--ready-timeout-ms <ms>]] [--no-pinned] [--no-associate] [--dry-run] [--yes] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"usage\": [\n \"runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes pin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"usage\": [\n \"runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"usage\": [\n \"runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"usage\": [\n \"runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels) on explicit user request.\",\n \"usage\": [\n \"runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent or terminal tab inside an existing Pane, sharing its worktree. The default for review, QA, fixes and any other agent on work that already has a Pane.\",\n \"usage\": [\n \"runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--focus|--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"usage\": [\n \"runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"localControl\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"usage\": [\n \"runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) [--source user|agent] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"core\",\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--source user|agent] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\",\n \"dispatchWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place, resuming its agent's conversation.\",\n \"usage\": [\n \"runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \"runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"usage\": [\n \"runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"usage\": [\n \"runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"panels\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"usage\": [\n \"runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane panels conversation --hook [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"panels\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"usage\": [\n \"runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"sessions:get-git-status\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"usage\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stage-and-commit\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"usage\": [\n \"runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-push\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"usage\": [\n \"runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-pull\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"usage\": [\n \"runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:rebase-main-into-worktree\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"usage\": [\n \"runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:restore\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"core\",\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"usage\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:squash-and-rebase-to-main\",\n \"args\": [\n \"--pane\",\n \"--message\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"usage\": [\n \"runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-stash-pop\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"usage\": [\n \"runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-soft-reset\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"usage\": [\n \"runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"openWorld\": true,\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:git-fetch\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"git\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"usage\": [\n \"runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:run-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"usage\": [\n \"runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"sessions:stop-script\",\n \"args\": [\n \"--pane\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"usage\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"folders:move-session\",\n \"args\": [\n \"--pane\",\n \"--folder\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"usage\": [\n \"runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\"\n ],\n \"daemonAction\": {\n \"channel\": \"folders:get-by-project\",\n \"args\": [\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"usage\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"daemonAction\": {\n \"channel\": \"folders:create\",\n \"args\": [\n \"--name\",\n \"--repo\"\n ]\n },\n \"toolsets\": [\n \"panes\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"usage\": [\n \"runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \"runpane links create --repo <repo-id> [--json]\",\n \"runpane links create --session <session-id> [--json]\"\n ],\n \"toolsets\": [\n \"core\",\n \"links\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"usage\": [\n \"runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"daemonAction\": {\n \"channel\": \"runpane:links:open\",\n \"args\": [\n \"--url\"\n ]\n },\n \"toolsets\": [\n \"links\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"usage\": [\n \"runpane docs search --query <text> [--limit <count>] [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"usage\": [\n \"runpane docs read --doc <path> [--json]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"docs\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"usage\": [\n \"runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"usage\": [\n \"runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\"\n ],\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"usage\": [\n \"runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"wrappers\": [\n \"npm\"\n ],\n \"toolsets\": [\n \"core\",\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"usage\": [\n \"runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"usage\": [\n \"runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \"runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"runpane notify --resume <id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"usage\": [\n \"runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"usage\": [\n \"runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"usage\": [\n \"runpane autopilot status [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"usage\": [\n \"runpane autopilot waiting [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"usage\": [\n \"runpane autopilot stack [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"usage\": [\n \"runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"usage\": [\n \"runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"usage\": [\n \"runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"toolsets\": [\n \"autopilot\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"usage\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"usage\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"usage\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"usage\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"usage\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"usage\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"usage\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"usage\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"usage\": [\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"usage\": [\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"usage\": [\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"usage\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"usage\": [\n \"runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"usage\": [\n \"runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"sessions\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"usage\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"usage\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"usage\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"agents\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"usage\": [\n \"runpane loops list [--json] [--pane-dir <path>]\"\n ],\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"usage\": [\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"usage\": [\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"additive\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"usage\": [\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"usage\": [\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"localControl\": true\n },\n {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"usage\": [\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"mutates\": true,\n \"idempotent\": true,\n \"toolsets\": [\n \"core\",\n \"loops\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"localControl\": true\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository, created/renamed pane session, named lock, or loop.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name. For a loop, the worktree and branch name template; defaults to loop-<name>.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"description\": \"Exact new branch name for the created worktree, slashes included. Must not exist yet; defaults to the worktree name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"aliases\": [\n \"--base\"\n ],\n \"description\": \"Ref the created worktree branches from. --base is an alias.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"description\": \"Top-level sidebar folder for an adopted pane.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<agent-session-id>\",\n \"description\": \"Agent session id to persist and resume in an adopted pane.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"description\": \"http(s) or file URL for panels open; pane:// link for links open.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"description\": \"File inside the Pane worktree to open; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"aliases\": [\n \"--prompt-file\"\n ],\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--attach\",\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a structured JSON request payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create/adopt --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated. On panels conversation it is instead what started the conversation: startup, resume, clear, or compact.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"description\": \"Use a persisted daemon-held workspace cursor.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"description\": \"Read workspace events after this generation.\"\n },\n {\n \"name\": \"--from\",\n \"value\": \"<now|earliest>\",\n \"description\": \"Choose where a new named cursor starts.\"\n },\n {\n \"name\": \"--kinds\",\n \"value\": \"<kind,...>\",\n \"description\": \"Limit workspace events to comma-separated kinds.\"\n },\n {\n \"name\": \"--name-contains\",\n \"value\": \"<text>\",\n \"description\": \"Limit workspace events to Pane names containing text.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane from workspace watch results; repeatable.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"description\": \"Print a HEARTBEAT line this often under --follow (a duration; default off).\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"description\": \"First IDLE reminder after a READY panel stays idle (a duration; default 10m under --follow; 0 turns IDLE off).\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"description\": \"End a --follow watch after this long with nothing but IDLE, printing WATCH EXPIRED and exiting 0 (a duration; default 3h; 0 never expires).\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Quiet time before READY under --follow (a duration; default 3m).\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"description\": \"Time before BLOCKED under --follow (a duration; default 30s).\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"description\": \"Batch window for non-urgent lines under --follow (a duration; default 10m).\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read diagnostic report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"description\": \"Named orchestration Session id or exact name.\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"description\": \"How long a named lock is held before it expires: a number with ms, s, m, or h (such as 30m); a bare number is milliseconds. At least 1s, at most 24h.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"description\": \"lock acquire: how long to wait for a held lock to come free; 0 (the default) returns at once. On panels submit, panels create and panes create, --wait takes no value: block until that panel reports, misses its report, waits on a prompt, or exits, then print one JSON result.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"description\": \"With --wait: give up after this long (30m, 2h; 0 waits without limit) and exit 124.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"description\": \"What a named lock is for. Outside a Pane terminal it also names the owner, so pass the same note to release it.\"\n },\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"description\": \"Five-field cron schedule in this machine's local time zone, such as \\\"0 3 * * *\\\". Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"description\": \"Commit message for panes commit.\"\n },\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"description\": \"Search words for docs search.\"\n },\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"description\": \"Doc path for docs read.\"\n },\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"description\": \"MCP toolsets for runpane mcp.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"description\": \"Named keys for panels input, such as down,enter.\"\n },\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"description\": \"Report state for runpane report.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"description\": \"Pull request number for runpane report.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"description\": \"Head commit (7-40 hex characters) for runpane report.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"description\": \"Report summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read the report summary from a file or stdin.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"description\": \"What a blocked worker needs answered; required with --state blocked.\"\n },\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"description\": \"The conversation (agent session) id a panel's agent is on, for runpane panels conversation.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"description\": \"Working directory on the target machine for workspace exec.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"description\": \"Handoff note to check and send; - reads stdin.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"description\": \"Handoff destination machine; overrides the machine in the destination text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"description\": \"Model for the receiving agent; overrides the destination text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"description\": \"Reasoning effort for the receiving agent (minimal, low, medium, high, xhigh, max); overrides the destination text.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"description\": \"Project Settings archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository. Defaults to worktrees/ in the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME|json>\",\n \"description\": \"secrets exec: inject secret NAME as VAR; repeatable. repos update: a JSON array of project variables to add or replace by key.\"\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"description\": \"Pane secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"description\": \"Secrets to inject, comma-separated. Defaults to every secret in the project.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"description\": \"secrets request: why you need the secret, shown on the card. autopilot close: why it closes, recorded on the item.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"description\": \"notify: what to show: pick one of choices, approve or deny, type an answer, or an update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"description\": \"notify: the card's subtitle: one or two sentences on why it matters (up to 240 characters).\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options: 1 to 9 choices as a JSON array of {label, detail?, recommended?}; with --update a reworded choice adds \\\"was\\\", its old label.\"\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: a preformatted block, such as the command or SQL to approve.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"description\": \"notify --ui approve: label of the disclosure that reveals --detail, such as Show the SQL; up to 40 characters.\"\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui ask: suggested answers as a JSON array of strings.\"\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui info: one button as a JSON object {label, url}.\"\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"description\": \"notify: up to 6 related files, pages or approvals as a JSON array of {label, path}, {label, url} or {label, approval}; any ui.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"description\": \"Text that workspace write writes instead of stdin.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"description\": \"Start an Agent Farm profile (such as greenfield/implementer:opus) in a chat tab.\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"description\": \"A launch argument for --agent-farm, shown to the agent in its LAUNCH CONTEXT; repeatable.\"\n },\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"description\": \"autopilot stack push only: the waiting item (an id from `runpane autopilot waiting`) to put on the stack.\"\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"description\": \"notify only: relay into this waiting item (an id from `runpane inbox`): the same card gains the choices, context and links, and the call waits on it.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"description\": \"notify only, for Autopilot's own items: the Session the item is about.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"description\": \"notify --ui options, instead of --choices: 2 to 4 questions as a JSON array of {title, choices}, each with 1 to 9 choices and one pick.\"\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: say how you took the person's reply to this approval; answer or question follows the id.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"description\": \"notify --took: the word after the id, as a flag (the MCP tool's tookAs).\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"description\": \"notify --took <id> question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"description\": \"notify only: save a new version of this approval after the person's question, and wait on it.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"description\": \"notify --update: what changed, one line per change.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"description\": \"inbox ack only: record this D-n on each named item.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"description\": \"autopilot answer only: answer with this choice's label.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"description\": \"autopilot answer only: why, recorded on the item: a D-n, a document or the person's words.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--split\",\n \"description\": \"Open the tab in split view beside the primary tab group (default for panels open).\"\n },\n {\n \"name\": \"--tab\",\n \"description\": \"Open the tab in the current tab group instead of split view. On autopilot stack push it takes a value instead: --tab <path-or-url> adds a tab (a file, or an http(s) URL); repeatable.\"\n },\n {\n \"name\": \"--pinned\",\n \"description\": \"Request pinning at creation; Session association still clears the first pin. Outside a Session, creation pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"description\": \"Create the pane unpinned instead of the default pinned (the UI's favorite/pin star).\"\n },\n {\n \"name\": \"--no-associate\",\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session.\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn, or a tab holds unsent input; for panels close, close the tab even if an agent opened it, its agent is busy, or it holds unsent input (a stuck tab); for lock release, release another owner's lock.\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"description\": \"Also check and remove an adopted Pane's worktree when archiving it. The local branch is kept.\"\n },\n {\n \"name\": \"--merged\",\n \"description\": \"With panes archive --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work. Other Panes are skipped with a reason (pr-open, pr-not-merged, pr-status-unknown, or a local safety code).\"\n },\n {\n \"name\": \"--closed\",\n \"description\": \"With panes archive --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n {\n \"name\": \"--launch\",\n \"description\": \"Run an adopted pane's agent resume command immediately instead of staging it.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--follow\",\n \"description\": \"Continue waiting for workspace events until interrupted.\"\n },\n {\n \"name\": \"--until-event\",\n \"description\": \"Watch only: wait across daemon calls for the first matching event, print it, and exit 0. --timeout-ms becomes the total deadline (exit 124). For harnesses that wake an agent when a process exits.\"\n },\n {\n \"name\": \"--ack-now\",\n \"description\": \"Advance a named cursor immediately for at-most-once delivery.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"description\": \"Include up to 120 characters of unsubmitted composer text in ready events.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"description\": \"Resume every stopped agent panel in every non-archived Pane.\"\n },\n {\n \"name\": \"--include-shells\",\n \"description\": \"Include ordinary shell panels in follow mode.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"description\": \"Disable redacted held-input detection under --follow (STUCK lines, and heldInputPresent in JSON).\"\n },\n {\n \"name\": \"--self-test\",\n \"description\": \"Probe the current daemon watch path without advancing a named cursor.\"\n },\n {\n \"name\": \"--report\",\n \"description\": \"Prepare a redacted doctor report; external filing still requires --yes.\"\n },\n {\n \"name\": \"--fix\",\n \"description\": \"Update each npm-installed runpane older than the Pane-managed CLI to its version. Never removes a file.\"\n },\n {\n \"name\": \"--read-only\",\n \"description\": \"Serve only read-only MCP tools.\"\n },\n {\n \"name\": \"--template\",\n \"description\": \"Print the handoff note template, with this checkout's (or Session's) origin and git state filled in.\"\n },\n {\n \"name\": \"--push\",\n \"description\": \"Before a handoff, commit uncommitted work as WIP and push the branch (never forces).\"\n },\n {\n \"name\": \"--note-only\",\n \"description\": \"Hand off to a fresh agent with the note, instead of carrying and resuming your own session.\"\n },\n {\n \"name\": \"--interrupt\",\n \"description\": \"Stop the current agent turn, wait for an empty idle composer, then submit and verify the replacement.\"\n },\n {\n \"name\": \"--expect-report\",\n \"description\": \"Pane expects a `runpane report` and emits agent.report.missing if the agent goes ready or exits without one. The default for work an agent sends; use it to ask for one on work marked --source user.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"description\": \"Send work without waiting for a `runpane report` (fire-and-forget input); no agent.report.missing follows.\"\n },\n {\n \"name\": \"--all\",\n \"description\": \"inbox ack only: clear every item of the --session.\"\n },\n {\n \"name\": \"--paused\",\n \"description\": \"loops create only: create the loop paused.\"\n },\n {\n \"name\": \"--chat\",\n \"description\": \"Open the agent as a chat tab (Claude or Codex over ACP, no terminal) instead of a terminal tab.\"\n },\n {\n \"name\": \"--next\",\n \"description\": \"autopilot stack push only: put the entry right after the one on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--approve\",\n \"description\": \"autopilot answer only: approve the item.\"\n },\n {\n \"name\": \"--deny\",\n \"description\": \"autopilot answer only: deny the item.\"\n },\n {\n \"name\": \"--hook\",\n \"description\": \"runpane panels conversation: read a Claude Code SessionStart hook's JSON from stdin and report it for PANE_PANEL_ID. Prints nothing and always exits 0.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane sessions list [--json] [--pane-dir <path>]\",\n \" runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \" runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions pause|resume --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \" runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane loops list [--json]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read|write|exec|<command>\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \" --report Prepare a redacted, inspectable report\",\n \" --body-file <path|-> Read report evidence from a file or stdin\",\n \" --title <text> Report title\",\n \" --yes Confirm creating one greenfield-inc/Pane issue\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Remote daemon data directory (default: ~/.pane_remote)\",\n \" --pane-path <path> Existing Pane executable used to perform repair\",\n \" --yes Skip the restart confirmation\",\n \" --json Print one machine-readable repair result\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Repository display name\",\n \" --build-script <script> Build (setup) script; runs in each new worktree\",\n \" --run-script <script> Run script; runs on the Play button\",\n \" --archive-script <script> Archive script; runs before a worktree is removed\",\n \" --worktree-folder <path> Folder for new worktrees, absolute or relative to the repository\",\n \" --open-ide-command <command> Command the Open in IDE button runs\",\n \" --system-prompt <text> Project system prompt\",\n \" --env <json> Project variables to add or replace, a JSON array: [{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://...\\\"}, {\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]\",\n \" --env-remove <name,...> Project variables to remove\",\n \" --clear <setting,...> Clear settings, e.g. run-script,archive-script; cleared scripts fall back to the repo's config file\",\n \" --from-json <path|-> Settings object: {\\\"runScript\\\": \\\"pnpm dev\\\", \\\"archiveScript\\\": null, \\\"envVars\\\": {\\\"set\\\": [...], \\\"remove\\\": [\\\"OLD\\\"]}, ...}; flags override it\",\n \" --dry-run Show the result without saving\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Limit results to one saved repository\",\n \" --pane <pane-id> Return one Pane, including an idle Pane with zero usage\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane workspace list [--json]\",\n \" runpane workspace <machine> read <path>\",\n \" runpane workspace <machine> write <path> (content from --content or stdin)\",\n \" runpane workspace <machine> exec -- <command>\",\n \" runpane workspace <machine> <runpane command> (any command that talks to Pane, e.g. sessions list --json)\",\n \" runpane workspace enable|disable\",\n \"\",\n \"<machine> is a Tailscale name, unique prefix, or IP. Your own machines are reachable, and a teammate's machine when its owner set Who can connect to Everyone on tailnet.\",\n \"Without <machine>, read, write, and exec route a path that cannot exist here (C:\\\\... on a Mac) to the one joined machine it fits.\",\n \"Commands that only run here (doctor, agent-context, mcp, docs) run there with: runpane workspace <machine> exec -- 'runpane doctor'\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes, a busy agent, or unsent input\",\n \" --remove-worktree Also check and remove an adopted Pane's worktree (the branch is kept)\",\n \" --session <id|name> Archive the Panes associated with this Session (requires --merged)\",\n \" --merged Only archive Panes that are clean and pushed, or merged via a PR\",\n \" --closed With --session --merged, also archive Panes whose PR closed unmerged\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Refresh and print archive safety evidence without archiving\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to pin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without pinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to unpin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without unpinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to rename\",\n \" --name <new-name> New non-empty Pane/session name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without renaming the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to focus\",\n \" --panel <panel-id> Optional panel/tab id inside the Pane to select\",\n \" --source <user|agent> Mutation source; does not change focus behavior\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Accepted and ignored; agent-context never contacts Pane\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \"\",\n \"Runs the Pane MCP server over stdio. Each runpane command with a JSON result becomes a tool that returns the same JSON as `runpane <command> --json`. Commands that need --yes take `yes: true` instead.\",\n \"Pane registers this server with Claude Code, Codex, and Cursor automatically (Settings > AI & Agents). To register it by hand:\",\n \" claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \" codex mcp add pane -- npx --yes runpane@latest mcp\",\n \"\",\n \"The MCP server ships in the npm package and inside the Pane app. The Python package does not include it.\",\n \"\",\n \"Options:\",\n \" --toolsets <name,...> core (default), all, read, repos, panes, panels, agents, sessions, workspace, git, docs, links, admin\",\n \" --read-only Serve only read-only tools\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tab to close\",\n \" --dry-run Report whether the tab would close, and why not, without closing it\",\n \" --force Close it even if an agent didn't open it, its agent is busy, or it holds unsent input\",\n \" --source <user|agent> Mark mutation source\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"\",\n \"Options:\",\n \" --query <text> Words to search for.\",\n \" --limit <count> Maximum results; defaults to 5.\",\n \" --json Print machine-readable output.\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"Read one Pane doc, help topic, or skill in full.\",\n \"\",\n \"Options:\",\n \" --doc <path> Path from docs search, such as docs/PANE_MCP.md.\",\n \" --json Print machine-readable output.\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --name <name> Name for the new Pane.\",\n \" --agent <codex|claude|cursor> Built-in agent to launch.\",\n \" --tool-command <command> Custom terminal command instead of a built-in agent.\",\n \" --prompt <task> The task to send once the agent is ready.\",\n \" --base-branch <branch> Branch to start the worktree from.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --limit <count> Screen lines to return; defaults to 40.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id; its agent panel is used.\",\n \" --panel <panel-id> Panel id.\",\n \" --text <message> The message to submit.\",\n \" --as-file-pointer Write the message to a prompt file and send `Read and follow <path>`.\",\n \" --no-expect-report Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Subtitle: why it matters, one or two sentences\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" runpane loops list [--json] [--pane-dir <path>]\",\n \" runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form; ~ is that machine's home.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" <path> Absolute path in any form.\",\n \" --content <text> Text that replaces the file. Without it, stdin is written.\",\n \" --json Print machine-readable output.\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"\",\n \"Options:\",\n \" <machine> Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\",\n \" --cwd <path> Working directory on that machine, in any path form; defaults to its home.\",\n \" --timeout-ms <milliseconds> Stop the command after this long (default 120000, at most 540000).\",\n \" --json Print machine-readable output.\",\n \" -- <command> The command line, run by that machine's shell.\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"Hand your task to another agent. You write the note (runpane handoff --template); the CLI checks it,\",\n \"makes sure your branch is pushed, sends the note to the destination, and starts the agent there.\",\n \"From a Pane tab, a handoff to the same agent (Claude, Codex, Cursor or OpenCode) carries your own session:\",\n \"another machine resumes a copy of it, this machine resumes a fork. The CLI then checks the receiver\",\n \"continued it (continued, inconclusive, or not-continued, which exits 1 and keeps the receiver).\",\n \"--note-only, another agent, or no session to carry starts a fresh agent with the note instead.\",\n \"From a Session's terminal it skips git and creates the Session on the other machine; a same-agent\",\n \"Session resumes your conversation when first opened, otherwise it reads the note. The sender Session stays.\",\n \"\",\n \"Options:\",\n \" <destination> Freeform: agent, model, effort, and machine, such as \\\"codex gpt-5 high on parsas-macbook-pro\\\".\",\n \" --template Print the note template, with this checkout's (or Session's) origin and git state filled in.\",\n \" --note-file <path|-> The note to check and send; every template section must be filled in.\",\n \" --machine <name> Destination machine (Tailscale name or unique prefix); overrides the text.\",\n \" --agent <codex|claude|cursor> Receiving agent; overrides the text.\",\n \" --model <model> Model for the receiving agent; overrides the text.\",\n \" --effort <level> minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\",\n \" --repo <selector> Saved repository on the destination; defaults to the one with the same remote.\",\n \" --push Commit uncommitted work (not the note) as WIP and push the branch first (never forces).\",\n \" --note-only Start a fresh agent with the note instead of carrying your own session.\",\n \" --dry-run Check destination reachability, saved repo and OS; print note and git state; send nothing. From a Session: the name it would get.\",\n \" --json Print machine-readable output.\",\n \"\",\n \"Examples:\",\n \" runpane handoff --template > ~/handoff.md\",\n \" runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \" runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Subtitle: why it matters, one or two sentences\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane doctor --report --body-file <path|->\",\n \" runpane daemon repair [--pane-dir <path>] [--yes] [--json]\",\n \" runpane agent-context [--json]\",\n \" runpane mcp [--toolsets <name,...>] [--read-only]\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane repos get [--repo <selector>] [--json]\",\n \" runpane repos update --repo <selector> [setting flags] --yes [--dry-run] [--json]\",\n \" runpane repos rename --repo <selector> --name <name>\",\n \" runpane secrets list [--project <project>] [--json]\",\n \" runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" runpane secrets delete --project <project> --name <name> --yes\",\n \" runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \" runpane watch --until-event\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base-branch <branch>] [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \" runpane docs read --doc <path> [--json]\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary-file <path|->] [--question <text>] [--json]\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--choices <json>] [--relays <item>] [--resume <id>] [--took <id|Q-n> <answer|question>] [--update <id|Q-n> ...] [--json]\",\n \" runpane inbox [--session <id|name>] [--json]\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json]\",\n \" runpane autopilot status|waiting|stack [--json]\",\n \" runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--next] [--json]\",\n \" runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) --because <why> [--json]\",\n \" runpane autopilot close <id> --reason <why> [--json]\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--choices <json>] [--json]\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \" runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \" runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" python -m runpane loops list [--json]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>] --yes\",\n \" python -m runpane loops update|run|pause|resume|delete --loop <id> --yes\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--pane-only | --cli-only] [--version <latest|vX.Y.Z>] [--pane-dir <path>] [--dry-run] [--wait] [--json]\",\n \" runpane update --status <operation-id> [--wait] [--pane-dir <path>] [--json]\",\n \"\",\n \"Updates both Pane and the invoked supported CLI by default, on this machine.\",\n \"Preflight identifies the host and installation owner before downtime. The independent helper stages an app, CLI, and standalone Node runtime before stopping Pane.\",\n \"Status distinguishes acceptance, installed components, host health, and per-panel restoration. No prompts or uncertain actions are replayed.\",\n \"CLI-only updates do not restart Pane; a managed CLI pin survives later Pane startup.\",\n \"Use workspace <machine> exec -- runpane update for an explicit remote machine.\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version [--pane-dir <path>]\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \" runpane doctor --report [--title <text>] --body-file <path|-> [--yes] [--json]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \" --report\",\n \" --body-file <path|->\",\n \" --title <text>\",\n \" --yes\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"daemon repair\": [\n \"Usage:\",\n \" runpane daemon repair [--pane-dir <path>] [--pane-path <path>] [--yes] [--json]\",\n \"\",\n \"Repairs and restarts only the managed remote-daemon launcher and service definition.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --yes\",\n \" --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"repos get\": [\n \"Usage:\",\n \" runpane repos get [--repo <selector>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows a saved repository's Project Settings. For the build, run, and archive scripts it shows the saved value, the fallback from the repository's config file (pane.json, conductor.json, .gitpod.yml, or devcontainer.json), and which of the two Pane will run.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos update\": [\n \"Usage:\",\n \" runpane repos update --repo <selector> [setting flags] [--clear <setting,...>] [--from-json <path|->] --yes [--dry-run] [--json]\",\n \"\",\n \"Changes a saved repository's Project Settings through the same save as the Project Settings dialog, so open Pane windows show the new values. Settings you do not pass keep their value. A cleared script falls back to the repository's config file.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --name <name>\",\n \" --build-script <script>\",\n \" --run-script <script>\",\n \" --archive-script <script>\",\n \" --worktree-folder <path>\",\n \" --open-ide-command <command>\",\n \" --system-prompt <text>\",\n \" --env <json>\",\n \" --env-remove <name,...>\",\n \" --clear <setting,...>\",\n \" --from-json <path|->\",\n \" --dry-run\",\n \" --yes\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos rename\": [\n \"Usage:\",\n \" runpane repos rename --repo <selector> --name <name> --yes [--json]\",\n \"\",\n \"Renames a saved repository. Only its display name changes; its path, Panes and settings stay as they are.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name\",\n \" --name <name> New non-empty repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"secrets\": [\n \"Usage:\",\n \" python -m runpane secrets <list|set|delete|exec|request> [options]\",\n \"\",\n \"Pane secrets are named values, addressed as <project>/<NAME>, that every machine you saved in Pane keeps a copy of.\",\n \"Agents use them only through `runpane secrets exec`, which puts them in a command's environment. No command prints a value.\",\n \"Ask the person for a new one with `runpane secrets request`; they type it on a masked card.\",\n \"\",\n \"Commands:\",\n \" python -m runpane secrets list [--project <project>] [--json]\",\n \" python -m runpane secrets set --project <project> --name <name> --yes (value on stdin)\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>] [--file <name>] -- <command>...\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"A project is up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\". A name follows environment variable rules: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n ],\n \"secrets list\": [\n \"Usage:\",\n \" python -m runpane secrets list [--project <project>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the Pane secrets on this machine: project, name, when each was last set, and on which machine. It never shows a value.\",\n \"\",\n \"Options:\",\n \" --project <project> Only this project's secrets\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets set\": [\n \"Usage:\",\n \" printf %s \\\"$VALUE\\\" | python -m runpane secrets set --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Saves the value on stdin as the Pane secret <project>/<name>, adding it or replacing the saved value. One trailing newline is dropped. Pane syncs it to your other machines.\",\n \"Agents should not set values they were given in chat. Ask the person with `runpane secrets request` instead.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets delete\": [\n \"Usage:\",\n \" python -m runpane secrets delete --project <project> --name <name> --yes [--json]\",\n \"\",\n \"Deletes the Pane secret <project>/<name>. Your other machines delete it the next time they sync.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name\",\n \" --yes Skip confirmation for mutating commands\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"secrets exec\": [\n \"Usage:\",\n \" python -m runpane secrets exec --project <project> [--only <name,...>] [--env <VAR=NAME>]... [--file <name>]... -- <command>...\",\n \"\",\n \"Runs the command with Pane secrets added to your environment, and exits with its exit code. Each secret is injected under its own name and overrides a variable of that name.\",\n \"With no --only, --env or --file, it injects every secret in the project.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --only <name,...> Inject only these secrets\",\n \" --env <VAR=NAME> Inject secret NAME as VAR; repeatable\",\n \" --file <name> Write secret NAME to a private temp file and set NAME to its path; repeatable. The file is removed when the command exits\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"Example:\",\n \" python -m runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\"\n ],\n \"secrets request\": [\n \"Usage:\",\n \" python -m runpane secrets request --project <project> --name <name> --title <text> --reason <text> [--json]\",\n \"\",\n \"Asks the person for a secret on a masked Notify card. What they type is saved as the Pane secret <project>/<name>; you get back only \\\"saved\\\" or \\\"declined\\\". Then use it with `runpane secrets exec`.\",\n \"A waiting result means the card is still open: run `runpane notify --resume <id>` to keep waiting.\",\n \"\",\n \"Options:\",\n \" --project <project> Secret project\",\n \" --name <name> Secret name, such as STRIPE_SECRET_KEY\",\n \" --title <text> What the secret is, such as \\\"Stripe test secret key\\\"\",\n \" --reason <text> What you need it for\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \" runpane panes pause|resume --pane <pane-id> --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"status is running while a tool or agent terminal is live; the dock shell does not count. agentState is the most urgent state of the live agent panels: blocked, working, ready, or none.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes cost\": [\n \"Usage:\",\n \" runpane panes cost [--repo <selector>] [--pane <pane-id>] [--json]\",\n \"\",\n \"Reports estimated token costs per Pane for the last 30 days, including per-model and cache-efficiency details.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"workspace\": [\n \"Workspace commands: this machine's Panes, and your other machines over Tailscale.\",\n \"\",\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \" runpane watch --follow\",\n \"\",\n \"runpane workspace list|read|write|exec|enable|disable ship in the npm package: npx --yes runpane@latest workspace list\"\n ],\n \"workspace state\": [\n \"Usage:\",\n \" runpane workspace state [--repo <selector>] [--json]\",\n \"\",\n \"Returns one workspace snapshot of every Pane and CLI panel.\"\n ],\n \"watch\": [\n \"Usage:\",\n \" runpane watch Print what changed since your cursor, then exit\",\n \" runpane watch --follow Keep watching, tuned for an agent watching unattended\",\n \" runpane watch --until-event Wait for one event worth waking for, print it, exit 0\",\n \"\",\n \"Waits for agent, Pane, panel, and PR events from the daemon without polling.\",\n \"\",\n \"What --follow does by default (see defaults.watch.follow in the contract):\",\n \" prints WATCH OK once when armed, then one line per event, with no heartbeats\",\n \" kinds: agent.report, agent.ready, agent.blocked, agent.idle, panel.exited, panel.stopped,\",\n \" panel.paused, panel.unpaused, pane.gone, pane.associated, pane.detached,\",\n \" pr.conflicted, pr.checks, pr.merged, pr.base_changed, pr.base_deleted\",\n \" READY after 3m of quiet; BLOCKED after 30s; other lines batched at most every 10m\",\n \" (REPORT, BLOCKED, conflicts, base changes, and failed checks arrive at once)\",\n \" IDLE reminders at 10m, 30m, 1h, 3h, then daily\",\n \" ends with WATCH EXPIRED and exit 0 after 3h with nothing but IDLE\",\n \"\",\n \"What --until-event does by default: waits for agent.report, agent.blocked, panel.exited,\",\n \"or pane.gone, prints that one line, and exits 0. Run it as a background task, act on\",\n \"its line, then run it again; its named cursor means nothing is missed between runs.\",\n \"Single-event waits deliver immediately without follow settling or batching.\",\n \"--follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"agent.report.missing (an agent given work ended its turn or exited without `runpane report`) is opt-in:\",\n \"add it to --kinds to wake on MISSING REPORT too.\",\n \"Autopilot's kinds are opt-in the same way, never in a default: item.created and item.answered\",\n \"(ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>), session.created\",\n \"and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>), and\",\n \"orchestrator.turn_ended (TURN ENDED <session-name> pane <pane-id> panel <panel-id> session <session-id>).\",\n \"\",\n \"Options:\",\n \" --session <id|name> Watch every Pane in a Session; associate/detach apply live\",\n \" --pane <id> Watch one Pane; repeatable (default: every managed Pane)\",\n \" --exclude-pane <id> Leave out a Pane; repeatable\",\n \" --repo <selector> Limit to a saved repository\",\n \" --name-contains <text> Limit by Pane name substring\",\n \" --include-shells Include plain shell panels (default: agent panels only)\",\n \" --kinds <kind,...> Replace the default event kinds\",\n \" --json One JSON object per line\",\n \" --as <name> Name the cursor (default: from --session or this panel)\",\n \" --since <generation> Use an explicit cursor instead of a name\",\n \" --epoch <epoch> Pair with --since to detect journal resets\",\n \" --from <now|earliest> Where a new named cursor starts (default: now)\",\n \" --self-test Check the daemon watch path and exit\",\n \"\",\n \"Overrides (durations such as 30s, 10m, 3h; a bare number is milliseconds; 0 turns it off):\",\n \" --ready-settle <duration> Quiet time before READY (default 3m)\",\n \" --blocked-settle <duration> Time before BLOCKED (default 30s)\",\n \" --min-interval <duration> Batch window for non-urgent lines (default 10m)\",\n \" --idle-after <duration> First IDLE reminder (default 10m)\",\n \" --expire-after <duration> End after this long with nothing but IDLE (default 3h)\",\n \" --heartbeat <duration> Print a HEARTBEAT line this often (default off)\",\n \" --timeout-ms <duration> Per-request wait; with --until-event, the total deadline (exit 124)\",\n \" --limit <count> Maximum entries per response\",\n \" --ack-now At-most-once cursor delivery\",\n \" --include-held-input Include unsubmitted composer text in JSON\",\n \" --no-held-input Turn off STUCK detection\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \"\",\n \"How a watch ends: WATCH EXPIRED (exit 0) means nothing happened; arm a new one with the next\",\n \"dispatch. A non-zero exit or a WATCH ERROR line means it failed: say so and re-arm once. Judge\",\n \"a watch by WATCH OK and these endings, never by silence. Never pipe --follow into grep -m1 or head.\",\n \"STUCK means real unsubmitted composer text; a prompt suggestion never counts.\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude|cursor> [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" python -m runpane panes create --repo <selector> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [--base <ref>] [--branch <name>] [--prompt-file <path|->] [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates a Pane in a saved repository and opens a terminal-backed tool tab. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create one only when the work's branch has no Pane yet; for existing work use `runpane panels create --pane <id>`.\",\n \"\",\n \"A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree directory name; defaults to --name\",\n \" --branch <name> Exact new branch name (slashes kept); must not exist; defaults to the worktree name\",\n \" --base-branch <ref> Ref to branch from; --base is an alias\",\n \" --agent <codex|claude|cursor> Built-in terminal template; with --tool-command, the agent it runs\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --prompt-file <path|-> Alias for --initial-input-file\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Request pinning at creation; first Session association clears it\",\n \" --no-pinned Create unpinned (the UI's favorite/pin star)\",\n \" --no-associate Inside a Session orchestrator, do not associate the new pane with that Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\",\n \"\",\n \"Outside a Session, creation pins by default, including --source agent. Inside a Session (PANE_ORCHESTRATION_SESSION_ID), children default to unpinned to avoid duplicating them in the global pinned list. First Session association clears the creation pin, including --pinned; use panes pin afterward to pin an associated child.\"\n ],\n \"panes adopt\": [\n \"Usage:\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --agent <codex|claude|cursor> [options]\",\n \" runpane panes adopt --repo <selector> --path <dir> --name <name> --tool-command <command> [--agent <codex|claude|cursor>] [options]\",\n \"\",\n \"Adopts an existing externally managed git worktree without creating, syncing, or deleting it. Archiving only removes its worktree with `panes archive --remove-worktree`. To start new work on a named branch, use `panes create --base <ref> --branch <name>` instead of `git worktree add` plus adopt.\",\n \"\",\n \"Options: --resume <id> stages the agent resume command (built-in agents only); --launch runs the command immediately; --prompt <text> or --prompt-file <path|-> and --wait-ready require --launch; --folder <name> groups the pane; --no-pinned opts out of pinning; --no-associate skips automatic Session association; --dry-run previews.\",\n \"\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] [--remove-worktree] [--dry-run] --yes [--json]\",\n \" runpane panes archive --session <id|name> --merged [--closed] [--remove-worktree] [--source user|agent] [--dry-run] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refreshes the configured upstream before checking for unpushed commits and reports exact commit evidence. When the branch has no upstream or its upstream is gone, a merged GitHub pull request whose head is HEAD (found with `gh`) counts as pushed. Refuses unsafe archives unless --force is passed. An adopted worktree is kept unless --remove-worktree is passed, which applies the same safety check and removal. The local branch is always kept. With --session and --merged, archives every Pane associated with the Session whose work is clean and pushed or merged, and reports a reason for each Pane it skips. Use --dry-run to print the evidence without mutating Pane state. Refuses a Pane with an agent mid-turn or waiting for an answer (agent-busy), or with a tab holding unsent composer text or unsaved editor edits (unsent-input), unless --force is passed; the --session sweep skips it. Add --closed to the sweep to also archive Panes whose PR closed without merging.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --remove-worktree\",\n \" --session <id|name>\",\n \" --merged\",\n \" --closed\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes rename\": [\n \"Usage:\",\n \" runpane panes rename --pane <pane-id> --name <new-name> --yes [--dry-run] [--json]\",\n \"\",\n \"Renames a Pane without changing its worktree, branch, panels, or focus. The name is trimmed like the Pane UI. Use --dry-run to validate and preview the updated pane without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --name <new-name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes focus\": [\n \"Usage:\",\n \" runpane panes focus --pane <pane-id> [--panel <panel-id>] --source user|agent --yes [--json]\",\n \"\",\n \"Raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Steals window focus, so run it only on an explicit user request to open, focus, or show a Pane, never proactively.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --panel <panel-id>\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [options]\",\n \" runpane panels open (--url <url>|--file <path>) [--split|--tab] --yes [--json]\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] --yes [--json]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane panels resume (--panel <panel-id>|--session <id|name>|--all-stopped) [--wait-ready] --yes [--json]\",\n \" runpane panels pause --panel <panel-id> --yes [--json]\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels conversation (--conversation <id> [--source startup|resume|clear|compact]|--hook) [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"agentDetection says how Pane knows agentType: declared, command, process, or screen. launchCommand is the command the panel was launched with.\",\n \"resumeId is the saved agent session ID that reopens the panel; resumeSource says which rule uses it: agent-farm, configured, or agent.\",\n \"openedBy says who opened the tab through runpane: source agent or user, and panelId of the tab that opened it. Tabs without it are the person's.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--keys <name,...>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --keys <name,...> Keys to press: enter, escape, tab, up, down, ctrl-c, ...\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \" --pane-dir <path> Accepted and ignored\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"mcp\": [\n \"Usage:\",\n \" runpane mcp\",\n \"\",\n \"The Pane MCP server ships in the npm package and inside the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest mcp\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude|cursor> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude|cursor> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) [--as-file-pointer [--attach <file>]...] [--interrupt] [--expect-report|--no-expect-report] [--wait [--timeout <duration>]] --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"For a Claude or Codex panel with no composer on screen, nothing is sent: the result is ok: false with blocked.kind composer-unknown.\",\n \"\",\n \"Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working.\",\n \"\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"\",\n \"Optional --wait returns a per-dispatch result; the orchestrator guide uses submit without --wait and a named watch. Choose one completion consumer.\",\n \"It returns once, with a compact result: outcome, ids, the report (state, summary,\",\n \"summaryPath, PR, head, question), screenLines for a prompt, or lastOutput for a missing\",\n \"report. Send an answer, or an addition to the current work, with `--no-expect-report`:\",\n \"it reaches a worker that still owes a report. Then watch for the original result.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --as-file-pointer Write the text to a prompt file and submit `Read and follow <path>`\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\",\n \" --interrupt Stop the current turn, wait for idle, then submit the replacement\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude|cursor> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--agent <codex|claude|cursor>] [--title <title>] [--no-focus] [--wait-ready] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude|cursor> Built-in agent command template, or with --tool-command the agent it runs.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --as-file-pointer Send `Read and follow <prompt file>` instead of the prompt.\",\n \" --attach <file> Copy a file beside the prompt file and list it; repeatable.\",\n \" --expect-report Expect a `runpane report` even for --source user work (the default otherwise)\",\n \" --no-expect-report Fire-and-forget: no agent.report.missing if the agent ends without reporting\",\n \" --wait Block until this panel reports, misses its report, waits on a prompt, or exits;\",\n \" print one JSON line (exit 0 report, 3 missing, 4 blocked, 5 exited, 124 timeout)\",\n \" --timeout <duration> With --wait: give up after this long (30m, 2h)\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait for the agent's input box, then deliver and verify the prompt.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels open\": [\n \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels open [--pane <pane-id>] (--url <url>|--file <path>) [--title <title>] [--split|--tab] [--focus|--no-focus] [--source user|agent] --yes [--json]\",\n \"\",\n \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open the tab in; defaults to PANE_SESSION_ID.\",\n \" --url <url> http(s) or file URL to open in a browser tab.\",\n \" --file <path> File inside the Pane worktree (relative, Linux, Windows, or WSL UNC path).\",\n \" --title <title> Tab title override.\",\n \" --split Open in split view beside the agent (default).\",\n \" --tab Open in the current tab group instead.\",\n \" --no-focus Open without selecting the tab.\",\n \" --focus Select the tab (default); never raises the window.\",\n \" --source <user|agent> Mutation source.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels close\": [\n \"Usage:\",\n \" runpane panels close --panel <panel-id> [--dry-run] [--force] [--source user|agent] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a tab exactly like closing it in Pane (a terminal tab's process stops); never archives the Pane or touches its worktree. Only tabs an agent opened with --source agent can close. Refuses, without closing, the person's tabs (and tabs from before Pane recorded openers), permanent tabs, an agent mid-turn or waiting for an answer, unsent composer text or unsaved edits, and a live tab whose input Pane cannot read. A refusal exits 1 with the reason and the next step. --force closes it anyway, as the tab's close button does, for a stuck tab or one the person asked you to close; only a permanent tab still stays. A tab id that no longer exists reports alreadyClosed.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --dry-run\",\n \" --force\",\n \" --source <user|agent>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\",\n \"\",\n \"Examples:\",\n \" runpane panels close --panel <panel-id> --dry-run --json\",\n \" runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter|tab] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex queues with Tab while working and submits with Enter when ready. auto retries once if the text is still in the composer.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"sessions list\": [\n \"Usage:\",\n \"python -m runpane sessions list [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions create\": [\n \"Usage:\",\n \"python -m runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true pins the Session; omitted or false leaves it unpinned.\",\n \"Create-time isPinned requires an updated Pane app; on older apps, use sessions pin after creation and read back the result.\"\n ],\n \"sessions get\": [\n \"Usage:\",\n \"python -m runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions update\": [\n \"Usage:\",\n \"python -m runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\",\n \"\",\n \"JSON isPinned: true/false sets the Session pin star. Select the Session with --session, not a session field in JSON.\"\n ],\n \"sessions set-agent\": [\n \"Usage:\",\n \"python -m runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions associate\": [\n \"Usage:\",\n \"python -m runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions detach\": [\n \"Usage:\",\n \"python -m runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"sessions overview\": [\n \"Usage:\",\n \"python -m runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name.\",\n \" --from-json <path|-> Read structured JSON from a file or stdin.\",\n \" --pane <pane-id> User-visible Pane to associate or detach.\",\n \" --agent <codex|claude|cursor> Agent terminal to use.\",\n \" --json Print JSON output.\"\n ],\n \"panes git-status\": [\n \"Usage:\",\n \" runpane panes git-status --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes commit\": [\n \"Usage:\",\n \" runpane panes commit --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stage all changes in a Pane worktree and commit them.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes push\": [\n \"Usage:\",\n \" runpane panes push --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Push a Pane branch to its remote.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes pull\": [\n \"Usage:\",\n \" runpane panes pull --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pull the remote branch into a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes rebase-main\": [\n \"Usage:\",\n \" runpane panes rebase-main --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Rebase a Pane branch onto the latest main branch.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes restore\": [\n \"Usage:\",\n \" runpane panes restore --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restore an archived Pane, recreating its worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes squash-rebase\": [\n \"Usage:\",\n \" runpane panes squash-rebase --pane <pane-id> --message <message> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --message <message> Commit message; the first line is the title.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash\": [\n \"Usage:\",\n \" runpane panes stash --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stash uncommitted changes in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stash-pop\": [\n \"Usage:\",\n \" runpane panes stash-pop --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Apply and drop the latest stash in a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes soft-reset\": [\n \"Usage:\",\n \" runpane panes soft-reset --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes fetch\": [\n \"Usage:\",\n \" runpane panes fetch --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Fetch the remote for a Pane worktree.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes run-script\": [\n \"Usage:\",\n \" runpane panes run-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Run the repository's run script in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes stop-script\": [\n \"Usage:\",\n \" runpane panes stop-script --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Stop the run script running in a Pane.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"panes move\": [\n \"Usage:\",\n \" runpane panes move --pane <pane-id> --folder <folder-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Move a Pane into a sidebar folder.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id from panes list or agents start.\",\n \" --folder <folder-id> Folder id from folders list.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders list\": [\n \"Usage:\",\n \" runpane folders list --repo <repo-id> [--json] [--pane-dir <path>]\",\n \"\",\n \"List the sidebar folders of a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"folders create\": [\n \"Usage:\",\n \" runpane folders create --repo <repo-id> --name <name> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a sidebar folder in a repository.\",\n \"\",\n \"Options:\",\n \" --repo <repo-id> Numeric repository id from repos list.\",\n \" --name <name> Folder name.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"links create\": [\n \"Usage:\",\n \" runpane links create --pane <pane-id> [--panel <panel-id>] [--json]\",\n \" runpane links create --repo <repo-id> [--json]\",\n \" runpane links create --session <session-id> [--json]\",\n \"\",\n \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane to open.\",\n \" --panel <panel-id> Panel inside the Pane to select.\",\n \" --repo <repo-id> Repository id from repos list.\",\n \" --session <session-id> Session id or name from sessions list.\",\n \" --json Print machine-readable output.\"\n ],\n \"links open\": [\n \"Usage:\",\n \" runpane links open --url <pane-url> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Open a pane:// link in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --url <pane-url> A pane://open link from links create.\",\n \" --yes Confirm this change; required in non-interactive shells.\",\n \" --json Print machine-readable output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"docs search\": [\n \"Usage:\",\n \" runpane docs search --query <text> [--limit <count>] [--json]\",\n \"\",\n \"`runpane docs search` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs search\"\n ],\n \"docs read\": [\n \"Usage:\",\n \" runpane docs read --doc <path> [--json]\",\n \"\",\n \"`runpane docs read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest docs read\"\n ],\n \"agents start\": [\n \"Usage:\",\n \" runpane agents start (--pane <pane-id> | --repo <selector> --name <name> [--base-branch <branch>]) (--agent <codex|claude|cursor>|--tool-command <command>) --prompt <task> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents start` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents start\"\n ],\n \"agents status\": [\n \"Usage:\",\n \" runpane agents status (--pane <pane-id>|--panel <panel-id>) [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents status` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents status\"\n ],\n \"agents send\": [\n \"Usage:\",\n \" runpane agents send (--pane <pane-id>|--panel <panel-id>) --text <message> [--as-file-pointer] [--no-expect-report] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane agents send` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest agents send\"\n ],\n \"sessions\": [\n \"Usage:\",\n \" runpane sessions <list|create|get|update|set-agent|associate|detach|overview|pause|resume> [options]\",\n \"\",\n \"Use a named Session to keep context, activity, and evidence attached to one orchestration thread.\"\n ],\n \"report\": [\n \"Usage:\",\n \" runpane report --state <ready|blocked|failed|done> [--pr <number>] [--head <sha>] [--summary <text>|--summary-file <path|->] [--question <text>] [--pane <pane-id> --panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Hands back a worker's structured report for its panel: the orchestrator receives it as an\",\n \"agent.report watch event and reads it in `agents status` and `sessions overview`.\",\n \"Inside a Pane terminal the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"It prints how many watches were waiting and received it (\\\"Delivered to 1 waiting watcher.\\\"). With no Session and\",\n \"nobody waiting, the report is still stored: a named-cursor watch receives it when it next waits.\",\n \"\",\n \"Options:\",\n \" --state <state> ready, blocked, failed, or done\",\n \" --pr <number> Pull request number\",\n \" --head <sha> Head commit, 7-40 hex characters\",\n \" --summary <text> Summary text; up to 16,000 characters are kept\",\n \" --summary-file <path|-> Read the summary from a file or stdin\",\n \" --question <text> What you need answered; required with --state blocked\",\n \" --pane <pane-id> Pane id when not running inside a Pane terminal\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md\"\n ],\n \"notify\": [\n \"Usage:\",\n \" runpane notify --ui <options|approve|ask|info> --title <text> [--context <text>] [--choices <json>] [--questions <json>] [--detail <text>] [--detail-label <text>] [--suggestions <json>] [--action <json>] [--links <json>] [--relays <item>] [--about-session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane notify --took <id|Q-n> <answer|question> [--status <text>] [--json] [--pane-dir <path>]\",\n \" runpane notify --update <id|Q-n> --asked <text> --what-changed <text> [--title <text>] [--context <text>] [--choices <json>] [--questions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \" runpane notify --resume <id> [--json] [--pane-dir <path>]\",\n \"\",\n \"Asks the person a question, or tells them something, on screen wherever they are, and\",\n \"prints their answer. The call waits until they answer; --ui info returns at once.\",\n \"Inside a Pane terminal the question names its Pane from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"When Pane is not running it prints an unavailable result: ask in the terminal instead.\",\n \"A typed reply comes back as a reply result (or a `reply:` line in your tab): it may answer\",\n \"the question or ask one. Say which with --took; for a question, find out, then --update.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, ask, or info\",\n \" --title <text> The question or update in one line, about 80 characters\",\n \" --context <text> Subtitle: why it matters, one or two sentences\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --questions <json> options, instead of --choices: 2 to 4 of [{\\\"title\\\":\\\"...\\\",\\\"choices\\\":[...]}], one pick each\",\n \" --detail <text> approve: the command or SQL to approve\",\n \" --detail-label <text> approve: label of the disclosure for --detail (default Show details)\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --action <json> info: one button, such as {\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://...\\\"}\",\n \" --links <json> Any ui: up to 6 related files, pages or approvals, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" or [{\\\"label\\\":\\\"Q-19\\\",\\\"approval\\\":\\\"Q-19\\\"}]\",\n \" --relays <item> Session orchestrator: improve this worker's waiting card (an id from runpane inbox)\",\n \" instead of posting a second one; the call waits on it, and the answer goes to the worker's tab\",\n \" --about-session <id|name> Autopilot's own item: the Session it is about\",\n \" --took <id|Q-n> <answer|question>\",\n \" Say how you took the person's reply: answer records it as the answer;\",\n \" question shows that you are getting more context\",\n \" --status <text> With --took question: what you are doing (default Getting more context…)\",\n \" --update <id|Q-n> Save a new version of the approval after the person's question, and wait on it;\",\n \" pass only what changed, and mark a reworded choice with \\\"was\\\" (its old label)\",\n \" --asked <text> With --update: the person's question, as they typed it\",\n \" --what-changed <text> With --update: one line per change\",\n \" --resume <id> Keep waiting on a question an earlier call returned waiting for\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane notify --ui options --title \\\"Which database?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]'\",\n \" runpane notify --took Q-20 answer\",\n \" runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: it can be undone from Settings\\\" --context \\\"...\\\"\"\n ],\n \"panels resume\": [\n \"Usage:\",\n \" runpane panels resume --panel <panel-id> [--wait-ready] [--ready-timeout-ms <ms>] --yes [--json] [--pane-dir <path>]\",\n \" runpane panels resume (--session <id|name>|--all-stopped) [--wait-ready] [--ready-timeout-ms <ms>] [--concurrency <count>] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a stopped terminal panel in the same panel, keeping its panel id. An agent panel\",\n \"relaunches with its resume command (Claude --resume <session>, codex resume <session>, Cursor\",\n \"--resume), the same restart the app runs when it shows the panel. Nothing takes focus. A panel\",\n \"whose agent exited back to its shell counts as stopped: its saved command runs again in that\",\n \"shell. A panel that is already running is left alone and reported as already-running.\",\n \"A paused panel resumes and loses its paused mark.\",\n \"\",\n \"With --session or --all-stopped it resumes every stopped agent panel in that scope (a\",\n \"Session's associated Panes, or every Pane), --concurrency at a time, skips running and paused ones,\",\n \"and returns one result per panel. With --wait-ready the resumed panels then wait together,\",\n \"and each result carries its own readiness and blocker. One panel failing does not stop the others.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --session <id|name> Resume the stopped agent panels of this Session's associated Panes\",\n \" --all-stopped Resume the stopped agent panels of every Pane\",\n \" --concurrency <count> Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10\",\n \" --wait-ready Wait until the resumed agent is ready; startup prompts come back as readiness.blocked\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; resumed panels wait together; defaults to 30000\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \" runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"panels last-message\": [\n \"Usage:\",\n \" runpane panels last-message --panel <panel-id> [--limit <count>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Reads a Claude or Codex agent's last reply from its transcript. It never scrapes the screen:\",\n \"without a transcript it reports transcript-unavailable and exits non-zero.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Agent panel id\",\n \" --limit <count> Maximum characters to return; defaults to 20000 (the end is kept)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels conversation\": [\n \"Usage:\",\n \" runpane panels conversation --conversation <id> [--source startup|resume|clear|compact] [--panel <panel-id>] [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \" runpane panels conversation --hook [--pane-dir <path>]\",\n \"\",\n \"Records which conversation a terminal panel's agent is on. Switch to chat, and every restart\",\n \"of the panel, open the conversation recorded since its agent started. Inside a Pane terminal\",\n \"the panel comes from PANE_SESSION_ID and PANE_PANEL_ID.\",\n \"\",\n \"--hook is for a Claude Code SessionStart command hook: it reads the hook's JSON from stdin\",\n \"(session_id, source; at most 64 KiB, for at most 2 seconds) and reports it for PANE_PANEL_ID.\",\n \"It prints nothing and always exits 0, so it never shows in Claude's context or blocks Claude:\",\n \"outside Pane, with bad input, when Pane is not running, or with an older Pane.\",\n \"\",\n \"Options:\",\n \" --conversation <id> Conversation (agent session) id\",\n \" --source <source> What started it: startup, resume, clear, or compact\",\n \" --hook Read a SessionStart hook's JSON from stdin, silently\",\n \" --panel <panel-id> Panel id when not running inside a Pane terminal\",\n \" --pane <pane-id> Pane id, with --panel\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Example:\",\n \" runpane panels conversation --conversation 0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44 --source resume\"\n ],\n \"lock\": [\n \"Usage:\",\n \" python -m runpane lock <acquire|release|list> [options]\",\n \"\",\n \"Named locks coordinate a resource shared between agents, such as one test account.\",\n \"The caller is the owner: the Pane and panel from $PANE_SESSION_ID and $PANE_PANEL_ID.\",\n \"A lock is scoped to the owner's Session when its Pane belongs to one; otherwise it is global.\",\n \"It is released on --ttl expiry, when the owner panel exits, or when the owner Pane is archived.\"\n ],\n \"lock acquire\": [\n \"Usage:\",\n \"python -m runpane lock acquire --name <name> --ttl <duration> [--wait <milliseconds>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock release\": [\n \"Usage:\",\n \"python -m runpane lock release --name <name> [--force] [--session <id|name>] [--note <text>] [--pane <pane-id>] [--panel <panel-id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"lock list\": [\n \"Usage:\",\n \"python -m runpane lock list [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Options:\",\n \" --name <name> Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\",\n \" --ttl <duration> Time to live, such as 90s, 30m, or 2h; a bare number is milliseconds.\",\n \" --wait <milliseconds> Wait this long for a held lock to come free (acquire).\",\n \" --note <text> What the lock is for; outside a Pane it also names the owner.\",\n \" --pane <pane-id> Act for this Pane instead of $PANE_SESSION_ID.\",\n \" --panel <panel-id> Act for this panel instead of $PANE_PANEL_ID.\",\n \" --force Release a lock another owner holds (release).\",\n \" --session <id|name> Named Session whose locks to list or release.\",\n \" --json Print JSON output.\"\n ],\n \"loops\": [\n \"Usage:\",\n \" python -m runpane loops <list|create|update|run|pause|resume|delete> [options]\",\n \"\",\n \"Loops (scheduled tasks) start a new Pane in a fresh worktree on a cron schedule.\",\n \"A Session loop instead sends its prompt to a Session's agent each run.\",\n \"Loops are for large recurring jobs and run at most once an hour.\",\n \"For short waits, use your agent harness's own sleep and wake.\",\n \"Runs start only while Pane is running, in this machine's local time zone.\",\n \"\",\n \"Commands:\",\n \" python -m runpane loops list [--json] [--pane-dir <path>]\",\n \" python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \" python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\"\n ],\n \"loops list\": [\n \"Usage:\",\n \"python -m runpane loops list [--json] [--pane-dir <path>]\",\n \"\",\n \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"\",\n \"Options:\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops create\": [\n \"Usage:\",\n \"python -m runpane loops create (--repo <selector>|--session <id|name|new>) --name <name> --cron <expr> (--agent <claude|codex|cursor>|--tool-command <command>) [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--paused] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --paused Create the loop paused.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops update\": [\n \"Usage:\",\n \"python -m runpane loops update --loop <id> [--repo <selector>|--session <id|name|new>] [--name <name>] [--cron <expr>] [--agent <claude|codex|cursor>|--tool-command <command>] [--prompt <text>|--prompt-file <path|->] [--worktree-name <template>] [--dry-run] --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"With --repo, each run is a new Pane in a fresh worktree from the repo's default base branch.\",\n \"A Session loop sends its prompt to that Session's agent each run instead of creating a new Pane.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop to change; other flags replace its values.\",\n \" --repo <selector> Repository: active, id, exact path, or saved name.\",\n \" --session <id|name|new> Session whose agent gets the prompt each run, instead of\",\n \" a new Pane; `new` creates a Session named after the loop.\",\n \" That agent then skips permission prompts for every message\",\n \" it gets, not only loop runs.\",\n \" --name <name> Loop name.\",\n \" --cron <expr> Five-field cron in local time, such as \\\"0 3 * * *\\\"; at least 1 hour apart.\",\n \" --agent <claude|codex|cursor> Agent each run starts; for a Session loop, the Session's agent.\",\n \" Runs skip permission prompts.\",\n \" --tool-command <command> Command each run starts instead of an agent, such as\",\n \" ./scripts/nightly-report.sh. Project loops only. On Windows it runs\",\n \" in Pane's terminal shell, Git Bash by default, so write bash or\",\n \" `cmd //c`: \\\"cmd //c echo hi > loop.txt\\\" works, \\\"cmd /c ...\\\" hangs.\",\n \" --prompt <text> Text typed into the agent or command after it starts; for a\",\n \" Session loop, the message sent to the Session's agent each run.\",\n \" --prompt-file <path|-> Read the prompt from a file or stdin.\",\n \" --worktree-name <template> Worktree and branch name; defaults to loop-<name>. Project loops only.\",\n \" --dry-run Check the schedule and print what would be saved.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops run\": [\n \"Usage:\",\n \"python -m runpane loops run --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Start a run of a loop now. The schedule stays the same.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops pause\": [\n \"Usage:\",\n \"python -m runpane loops pause --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops resume\": [\n \"Usage:\",\n \"python -m runpane loops resume --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Resume a paused loop from its next scheduled time.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"loops delete\": [\n \"Usage:\",\n \"python -m runpane loops delete --loop <id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Delete a loop. It starts no more runs.\",\n \"\",\n \"Options:\",\n \" --loop <id> Loop id, from runpane loops list.\",\n \" --yes Confirm the change.\",\n \" --json Print JSON output.\",\n \" --pane-dir <path> Connect to a specific Pane data directory.\"\n ],\n \"workspace list\": [\n \"Usage:\",\n \" runpane workspace list [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace list` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace list\"\n ],\n \"workspace enable\": [\n \"Usage:\",\n \" runpane workspace enable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace enable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace enable\"\n ],\n \"workspace disable\": [\n \"Usage:\",\n \" runpane workspace disable [--json] [--pane-dir <path>]\",\n \"\",\n \"`runpane workspace disable` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace disable\"\n ],\n \"workspace read\": [\n \"Usage:\",\n \" runpane workspace [<machine>] read <path> [--json]\",\n \"\",\n \"`runpane workspace read` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace read\"\n ],\n \"workspace write\": [\n \"Usage:\",\n \" runpane workspace [<machine>] write <path> [--content <text>] [--json]\",\n \"\",\n \"`runpane workspace write` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace write\"\n ],\n \"workspace exec\": [\n \"Usage:\",\n \" runpane workspace <machine> exec [--cwd <path>] [--timeout-ms <ms>] [--json] -- <command>\",\n \"\",\n \"`runpane workspace exec` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest workspace exec\"\n ],\n \"handoff\": [\n \"Usage:\",\n \" runpane handoff --template\",\n \" runpane handoff \\\"<destination>\\\" --note-file <path|-> [--machine <name>] [--agent <codex|claude|cursor|opencode>] [--model <model>] [--effort <level>] [--repo <selector>] [--push] [--note-only] [--dry-run] [--json]\",\n \"\",\n \"`runpane handoff` ships in the npm package and the Pane app, not in the Python package.\",\n \"Run it with Node instead: npx --yes runpane@latest handoff\"\n ],\n \"sessions pin\": [\n \"Usage:\",\n \"python -m runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star on. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"sessions unpin\": [\n \"Usage:\",\n \"python -m runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Set the named Session pin star off. Repeated requests keep that state. Uses the same Session update path as the UI.\"\n ],\n \"pr watch\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Register durable GitHub PR monitoring for a Pane.\"\n ],\n \"pr unwatch\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"\",\n \"Remove a Pane’s explicit PR registration.\"\n ],\n \"pr list\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\",\n \"\",\n \"List PR registrations and stopped watches.\"\n ],\n \"inbox\": [\n \"Usage:\",\n \" runpane inbox [--session <id|name>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists what a Session's orchestrator has not acknowledged: worker reports (agent.report),\",\n \"missing reports (agent.report.missing), and questions waiting on a worker's screen\",\n \"(agent.blocked; these clear themselves once the worker moves on), plus the answers its\",\n \"workers got, kept 7 days. An answer with no decision prints as needing a D-n. Pane keeps\",\n \"them across restarts. An orchestrator reads it when it starts or resumes, to catch what\",\n \"finished while it was down.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Session; defaults to PANE_ORCHESTRATION_SESSION_ID\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Clear handled items with `runpane inbox ack <item-id>...`, and record an answer's decision\",\n \"with `runpane inbox ack <item-id> --decision <D-n>`.\"\n ],\n \"inbox ack\": [\n \"Usage:\",\n \" runpane inbox ack <item-id>... [--decision <D-n>] [--session <id|name>] [--json] [--pane-dir <path>]\",\n \" runpane inbox ack --all --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Clears Session inbox items. Ids that match nothing are listed as unknown; acknowledging twice is harmless.\",\n \"With --decision it also records that D-n on each item, even one already cleared whose answer has none yet.\",\n \"\",\n \"Options:\",\n \" --decision <D-n> Record this D-n on each named item, such as D-147; not with --all\",\n \" --session <id|name> Only clear items of this Session; required with --all\",\n \" --all Clear every item of the Session\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot\": [\n \"Usage:\",\n \" runpane autopilot <status|waiting|stack|stack push|answer|close|rewrite> [options]\",\n \"\",\n \"Autopilot (experimental) walks every Session from the top and brings the person what waits on\",\n \"them, one group at a time. It may close and rewrite items and, when the way forward is clear\",\n \"and related to its work with the person or when told to, answer them, recorded \\\"by Autopilot\\\"\",\n \"on the item. Permission prompts stay the person's: Pane refuses them with AUTOPILOT_CANNOT_ANSWER.\"\n ],\n \"autopilot status\": [\n \"Usage:\",\n \" runpane autopilot status [--json] [--pane-dir <path>]\",\n \"\",\n \"Shows whether Autopilot is on and running, stopped or paused, its current run folder, how many\",\n \"entries its stack holds and which one is on screen, and how many items wait in each Session.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot waiting\": [\n \"Usage:\",\n \" runpane autopilot waiting [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists every approval, question and permission prompt waiting on the person on every machine\",\n \"Autopilot can reach, grouped by Session in the order Autopilot walks them (top first), one line\",\n \"per item: its id, kind, title, agent · Pane, how long it has waited, its machine when it isn't\",\n \"this one, and \\\"on the stack\\\", \\\"stale\\\", \\\"quiet\\\" or \\\"reopened by you\\\" when they apply. A last line names each saved\",\n \"machine it can't reach (\\\"<machine> offline\\\") and each of the person's machines not saved here\",\n \"(\\\"Can't see: <machine>\\\"). This is Autopilot's queue: read from each machine's table, no copy.\",\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack\": [\n \"Usage:\",\n \" runpane autopilot stack [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists the stack's entries in order; > marks the one on screen. Fails with AUTOPILOT_OFF while\",\n \"Autopilot is off.\",\n \"\",\n \"Options:\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"autopilot stack push\": [\n \"Usage:\",\n \" runpane autopilot stack push (--item <id> | --file <path> | --url <url> | --session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next] [--json] [--pane-dir <path>]\",\n \"\",\n \"Puts one entry on Autopilot's stack. A new entry never replaces the one on screen: it waits\",\n \"and comes up when the person is done with that one. Give exactly one of --item, --file, --url,\",\n \"or --session. Relative paths resolve against this directory.\",\n \"\",\n \"Options:\",\n \" --item <id> A waiting item from `runpane autopilot waiting`\",\n \" --file <path> A document to show (HTML, Markdown, or text)\",\n \" --url <url> A page or running app to show\",\n \" --session <id|name> A Session's overview\",\n \" --title <text> The entry's title (default: the item's, file's, or Session's name)\",\n \" --note <text> Autopilot's one line about why it is here\",\n \" --tab <path-or-url> An extra tab: a file, or an http(s) URL; repeatable\",\n \" --next Put it right after the entry on screen, so it comes up next\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_PAUSED while the person has paused Autopilot, and AUTOPILOT_STACK_INVALID\",\n \"when the item, file, URL, or Session is not one Pane can show.\"\n ],\n \"autopilot answer\": [\n \"Usage:\",\n \" runpane autopilot answer <id> (--choice <label> | --approve | --deny | --text <text>) [--note <text>] --because <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Answers a waiting item as Autopilot, from `runpane autopilot waiting`. The owning machine delivers\",\n \"it to the asker at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the\",\n \"person can Override it. Answer only when the way forward is clear and the item relates to your\",\n \"work with the person, or when the person told you to handle approvals.\",\n \"\",\n \"Options:\",\n \" --choice <label> The chosen label of an options item\",\n \" --approve Approve an approve item\",\n \" --deny Deny an approve item\",\n \" --text <text> A typed answer\",\n \" --note <text> A note for the asker, with --choice, --approve or --deny\",\n \" --because <why> Required: a D-n, a document or the person's words\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt, and says who answered when someone\",\n \"already has: leave those.\"\n ],\n \"autopilot close\": [\n \"Usage:\",\n \" runpane autopilot close <id> --reason <why> [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes a waiting item as Autopilot: its asker is told, \\\"Closed by Autopilot: <why>\\\" shows on every\",\n \"device, and the person can reopen it.\",\n \"\",\n \"Options:\",\n \" --reason <why> Required: why it no longer needs the person\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"autopilot rewrite\": [\n \"Usage:\",\n \" runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>] [--json] [--pane-dir <path>]\",\n \"\",\n \"Closes an unclear waiting item and re-asks it rewritten, linked to the old one, on the same\",\n \"machine. The answer goes to the original asker. Relative --links paths resolve against this\",\n \"directory; a file must exist on the item's machine.\",\n \"\",\n \"Options:\",\n \" --ui <ui> options, approve, or ask\",\n \" --title <text> The question in one line\",\n \" --context <text> Subtitle: why it matters, one or two sentences\",\n \" --choices <json> options: 1 to 9 of [{\\\"label\\\":\\\"...\\\",\\\"detail\\\":\\\"...\\\",\\\"recommended\\\":true}]\",\n \" --suggestions <json> ask: suggested answers, such as [\\\"main\\\",\\\"release\\\"]\",\n \" --links <json> Up to 6 related files or pages, such as [{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"}]\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \"\",\n \"Prints the new item's id. Fails with AUTOPILOT_CANNOT_ANSWER for a permission prompt.\"\n ],\n \"panels pause\": [\n \"Usage:\",\n \" runpane panels pause --panel <panel-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses one terminal tab: stops its process and marks it paused until\",\n \"`runpane panels resume --panel <panel-id>`, or its Pane or Session, resumes it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes pause\": [\n \"Usage:\",\n \" runpane panes pause --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses every terminal tab of a Pane, run scripts included, until `runpane panes resume`\",\n \"resumes it. A Pane is paused while all of its terminal tabs are; `panes list` reports it.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes resume\": [\n \"Usage:\",\n \" runpane panes resume --pane <pane-id> --yes [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts a Pane's paused tabs in place, the same restart `panels resume` runs: agents\",\n \"relaunch in their saved conversations and report ready as usual. Running tabs are left alone.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane id\",\n \" --yes Skip the confirmation prompt\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions pause\": [\n \"Usage:\",\n \" runpane sessions pause --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived (a chat tab can't pause yet: its agent keeps running, and the result says so),\",\n \"until `runpane sessions resume` resumes them.\",\n \"Stopping works the way quitting Pane does, without asking, even while an agent is working.\",\n \"Each agent's conversation is kept. A paused tab stays stopped through clicks, app restarts\",\n \"and updates, and `panels resume --session` and `--all-stopped` skip it.\",\n \"`runpane watch` reports each tab as panel.paused under --follow, to a Session-scoped watch, or when --kinds lists it.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"sessions resume\": [\n \"Usage:\",\n \" runpane sessions resume --session <id|name> [--json] [--pane-dir <path>]\",\n \"\",\n \"Restarts the paused tabs of a Session's chat and Panes in their saved conversations.\",\n \"Running tabs are left alone. An archived Session is refused; restore it first.\",\n \"`runpane watch` reports each resumed tab as panel.unpaused.\",\n \"\",\n \"Options:\",\n \" --session <id|name> Named Session id or exact name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane mcp\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane repos get --repo Pane --json\",\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --worktree-folder ../pane-worktrees --yes --json\",\n \"runpane repos update --repo Pane --clear run-script --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"https://api.test\\\"},{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\",\n \"runpane repos rename --repo 3 --name Lev --yes --json\",\n \"runpane secrets list --json\",\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes\",\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\",\n \"runpane secrets exec --project stripe --only STRIPE_SECRET_KEY -- pnpm test:webhooks\",\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook tests against Stripe test mode.\\\" --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes cost --pane <pane-id> --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane sessions list [--json] [--pane-dir <path>]\",\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\",\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\",\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\",\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\",\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\",\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock release --name testing-account --json\",\n \"runpane lock list --json\",\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops list --json\",\n \"runpane loops run --loop <loop-id> --yes --json\",\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\",\n \"runpane agents status --pane <pane-id> --json\",\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane panels last-message --panel <panel-id> --json\",\n \"runpane panels conversation --conversation <id> --source resume --json\",\n \"runpane panels close --panel <panel-id> --yes --json\",\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\",\n \"runpane panes pause --pane <pane-id> --yes --json\",\n \"runpane panes resume --pane <pane-id> --yes --json\",\n \"runpane docs search --query \\\"archive a pane\\\" --json\",\n \"runpane links create --pane <pane-id> --json\",\n \"runpane panes git-status --pane <pane-id> --json\",\n \"runpane help\",\n \"runpane <command> --help\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\",\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\",\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. The remote-host wizard asks only for a name, then runs interactive Tailscale setup with automatic port selection; explicit install daemon flags remain available for SSH and manual URLs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` defaults to both app and active supported CLI, with independent supervision, staging, durable status, and explicit restoration outcomes. Prerequisites, status fields/exit codes, draft handling and manual recovery are documented in [Update recovery](UPDATE_RECOVERY.md).\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, every `runpane` on `PATH` against the Pane-managed CLI, and remote-daemon hints. `--fix` updates npm installs of `runpane` older than the Pane-managed CLI to its version and never removes a file. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane daemon repair` rewrites and restarts only the managed remote-daemon launcher/service. It never creates pairing credentials, changes tunnels, or downloads Pane; doctor only recommends it and never runs it automatically.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output. An unknown command exits 2 and lists the closest command names; with `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout.\",\n \"`runpane mcp` runs a stdio MCP server whose tools are generated from this contract: every command with result `jsonSchemas` becomes a tool that runs `runpane <command> --json` and returns its output. Only the npm package and the Pane app include it; the Python wrapper prints how to run it with Node and exits non-zero.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane repos get` prints a saved repository's Project Settings. Each script shows its saved value, the fallback from the repository's config file, and the one Pane will run.\",\n \"`runpane repos update` changes a saved repository's Project Settings through the same save as the Project Settings dialog. It requires `--yes`, supports `--dry-run`, clears settings with `--clear`, and adds, replaces or removes project environment variables with `--env` and `--env-remove`.\",\n \"`runpane repos rename` trims and sets a saved repository's display name, the same rename as the repository settings in the app, and returns the updated repository record.\",\n \"`runpane secrets` manages Pane secrets: named values, addressed as `<project>/<NAME>`, that every machine saved in Pane keeps a copy of. `list` shows names, times and machines only. `set` reads the value from stdin. `delete` removes one everywhere. `exec` runs a command with secrets in its environment and returns its exit code. `request` asks the person on a masked Notify card and returns only `saved` or `declined`. No command prints a value, and the MCP server has no tool that sets or reads one. These commands run on the machine itself, so `runpane workspace <machine> secrets ...` is refused; use `runpane workspace <machine> exec -- runpane secrets ...`.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes cost` reports estimated token costs per Pane for the last 30 days, including per-model breakdowns and cache efficiency; unscoped output includes an Unattributed bucket that reconciles against workspace totals.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed. New Panes are pinned into the UI's favorite/pin set by default, except when the CLI runs inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID`), where child worktrees default to unpinned. Explicit `--pinned` / `--no-pinned` override creation defaults. First Session association clears an existing pin, including an explicit creation pin; use `panes pin` after association to pin a child. This avoids duplicating Session children in the global pinned list. Manual pins applied afterward are preserved. Panes created interactively in the Pane UI are unaffected. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"For `panes create --wait-ready` (and `panels create`, `panes adopt --launch`), ready means an identified agent at its own input box: declared with `--agent`, or detected from the command, its foreground process or its screen. A `--tool-command` that never shows a supported agent is not ready: `readiness.ok` is false with `readiness.reason.code` `no-agent-detected` after the timeout, or `command-exited` as soon as the command exits back to the shell, and nothing is typed into it. `agent-not-ready` means the agent was detected but its input box was not ready in time. CLI readiness is confirmed on consecutive polls. Codex also requires a recognized configured model or context footer below its live composer; its provisional startup composer is not ready. Hidden or unrecognized Codex status lines conservatively time out rather than prove readiness. Startup prompts return blocked with the prompt's own text in blocked.message and a suggestedCommand, and Pane never answers them, apart from Claude's folder trust for a Session's own folder: first-run trust and daemon dialogs and Agent Farm's `Trust this repository workspace? [y/N]` as blocked.kind=first-run-dialog; Codex's security setup banner and any other prompt, including a wrapper's or shell's `[y/N]` before the agent appears, as agent-prompt. A starting message blocked this way is held and sent once the prompt is answered and the agent's input box is ready. Every create with a starting message returns `initialInput`. Pane stages the text in the composer, presses the agent's submit key separately (Ctrl+Enter for Codex, declared or detected), and checks where it went: `initialInput.delivery` is `taken` or `queued` (from the agent's transcript or its screen), `in-composer`, or `unknown` (`argv` means a prompt was routed at launch; Pane gives the transcript up to 10 seconds before settling on unknown). `initialInput.verifiedSubmitted` is true exactly when it is `taken` or `queued`, and the create's `ok` is false unless a Claude or Codex starting message is verified. Cursor and OpenCode have no acceptance probe: their delivery stays `unknown` with a delivery.message saying so, `verification: unverifiable`, and `ok` follows readiness. Without `--wait-ready` the create returns at once with `initialInput.verifiedSubmitted: false`, a delivery.message saying the call did not wait, and a `panels wait` nextCommand; Pane still sends the message once the agent is identified and at its input box, and gives up without typing it if no supported agent appears within 30 seconds.\",\n \"`runpane panes archive` refreshes the configured upstream, reports exact unpushed commit evidence, and refuses unsafe archive operations unless `--force` is used. A branch whose upstream is gone counts as pushed when a merged GitHub pull request has HEAD as its head (`safetyCheck.mergedViaPr`). Add `--dry-run` to inspect the same evidence without archiving. `--remove-worktree` applies the same check and removal to an adopted worktree; local branches are always kept. Successful archives report `worktreeCleanup: completed` once the worktree is gone from its path and from git; `trashDeletion: pending` means its files are still being deleted in the background. `runpane panes archive --session <id|name> --merged` archives every Session Pane that is clean and pushed or merged, and reports a reason for each skipped Pane. Archive refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless `--force` is used, and the Session sweep skips it. Add `--closed` to the sweep to also archive Panes whose PR closed without merging.\",\n \"`runpane panes rename` trims and updates a Pane's display name without changing its worktree, branch, panels, or focus, and returns the updated pane summary.\",\n \"`runpane panes focus` raises the Pane window and selects a Pane (and optionally one of its panels) exactly like clicking it in the UI. Because it steals the user's window focus, run it only on an explicit user request to open, focus, show, or switch to a Pane; never focus a Pane proactively, the same doctrine that keeps `panes create` background/no-focus for `--source agent`.\",\n \"`runpane panels open` opens a URL or a file from the Pane worktree as a tab in an existing Pane (default: the calling panel's Pane from `PANE_SESSION_ID`), in split view beside the agent unless `--tab` is passed. Files open in an editor tab with read-only previews for supported formats, and HTML opens as editable source; an existing tab showing the same target is reused. It brings the tab forward for clients already showing that Pane, and never raises or focuses the Pane window or switches anyone's Pane.\",\n \"`runpane panels close --panel <panel-id>` closes a tab exactly like closing it in Pane (a terminal tab's process stops) and never archives the Pane or touches its worktree. Only a tab an agent opened with `--source agent` can close; Pane records that opener, and `panels list` shows it as `openedBy`. It refuses, without closing, the person's tabs and every tab from before Pane recorded openers (`opened-by-user`), permanent tabs (`permanent`), an agent mid-turn or waiting for an answer (`agent-busy`), unsent composer text or unsaved editor edits (`unsent-input`), and a live tab whose input Pane cannot read, such as agents other than Claude and Codex or chat tabs (`input-unknown`). A refusal prints `{ ok: false, closed: false, refused: { code, message, next } }` and exits 1. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports `wouldClose` and any refusal without closing. `--force` closes the tab anyway, as its close button does, for a stuck tab or one the person asked to close (`forced: true`); only a permanent tab still stays.\",\n \"`runpane panels list` lists tool panels inside one Pane session. Each panel with a saved agent session ID reports it as `resumeId`, with `resumeSource` (`agent-farm`, `configured`, or `agent`) naming the rule that reopens the conversation.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"`runpane panes create --branch <name>` creates the Pane's worktree on exactly that new branch, slashes included (for example `agents/w5a`). The name is checked with `git check-ref-format --branch`, creation fails if the branch already exists, and Pane never renames it to make it unique. `--worktree-name` still names the directory and defaults to `--name`. `--base` is an alias for `--base-branch`, and `--prompt-file` for `--initial-input-file`. `panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`. When the base is a remote-tracking branch (the default `origin/main`, or one named with `--base`), Pane fetches it first, waiting at most about 10 seconds, so the Pane starts at the remote's current tip. Each created item reports `base` (`ref`, `commit`, `refresh`); if the fetch fails the Pane still starts, from the local ref, and `base.notice` says so and how to catch up.\",\n \"`runpane panes adopt --launch` accepts `--prompt`, `--prompt-file`, `--wait-ready`, and `--ready-timeout-ms`, and reports `readiness`, `initialInput`, and `nextCommand` like `panes create`. A prompt without `--launch` is an error rather than being dropped.\",\n \"If composer submission cannot be verified without risking a duplicate, the create item is unsuccessful with `initialInput.staged`, `initialInput.attempts`, `initialInput.blocked.kind: submission_unverified`, and an actionable `nextCommand`. The CLI-facing `--prompt` alias maps to this canonical `initialInput` result.\",\n \"When running from WSL while Pane is installed on Windows, a Linux-installed runpane cannot reach Pane: it looks for a missing `/tmp/pane-daemon.../daemon.sock`. Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane` from any WSL shell) runs the Windows CLI through WSL interop and converts path arguments. With a Pane build older than that shim, invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"`runpane watch` waits for agent, Pane, panel, and PR events from the daemon journal without polling. `--follow` keeps watching with defaults tuned for an agent watching unattended (quiet, batched, and it ends itself after 3 hours with nothing happening); individual duration flags override these defaults. Cadence requires a named follow consumer; `--follow --since <generation>` has no consumer name and bypasses settling and batching. `--until-event` waits for one event worth waking for, prints it immediately without follow settling or batching, and exits 0. The defaults are listed in the contract under defaults.watch.\",\n \"A watch releases its pending slot when its client socket or named pipe disconnects. Re-arming the same named cursor takes over the previous request; the superseded watcher exits with a WATCH ERROR instead of competing for the cursor. Anonymous concurrent watchers retain the eight-wait limit. Each request also has a 125-second lease (slightly longer than the maximum 120-second long poll), so an abandoned request is reclaimed even if no disconnect arrives.\",\n \"The desktop app treats one consumer name specially: while a `runpane watch --as dictate` request is in flight, and for 30 seconds after the last one ends, Pane skips its own \\\"needs your input\\\" and \\\"finished\\\" desktop notifications, because [Dictate](https://github.com/parsakhaz/open-dictate) shows those events on its pill. When that watcher stops, Pane's notifications come back. The CLI's flags and output are unchanged.\",\n \"`sessions list` list durable named orchestration Sessions.\",\n \"`sessions create` create a durable named orchestration Session and its hidden terminal owner.\",\n \"`sessions get` read one durable named orchestration Session.\",\n \"`sessions update` update a Session overview from structured JSON.\",\n \"`sessions pin` and `sessions unpin` set the Session pin star through the existing UI update path. Create JSON accepts `isPinned` (default false); update JSON preserves the pin when the field is omitted.\",\n \"`sessions set-agent` switch the durable terminal agent for a named Session.\",\n \"`sessions associate` associate a user-visible Pane with a named Session.\",\n \"`sessions detach` detach a Pane from a named Session.\",\n \"`sessions overview` read a live status, activity, git, and pull request overview for a named Session. Each panel in `panes[].panels[]` carries its own latest `runpane report` as `report`, so one call shows what every worker last reported and when (`reportedAt`, next to the panel's `state` and `lastActivityAt`). When a report's summary is a complete JSON object, such as a status file sent with `--summary-file`, `report.data` holds it exactly as the worker gave it; Pane never reads, renames or validates its fields. A summary that is not a JSON object (plain text, an array, a scalar or invalid JSON), is nested more than 64 levels deep, or was truncated (`summaryTruncated`), has no `data`. A panel that never reported has no `report`. `panes[].report` is still the newest report among the Pane's panels. Each panel also has `type` (`terminal`, `browser`, `editor`, `explorer`, `diff`, ...): a Pane linked whole lists every tab, with `explorer` and `diff` last, and a link that names tabs lists exactly those.\",\n \"`runpane lock acquire|release|list` coordinate a resource shared between agents, such as one test account. The caller's Pane and panel own the lock; it is scoped to the owner's Session (or global outside one), renews for the same owner, and is released on TTL expiry, owner panel exit, or owner Pane archive. `--wait` blocks in the daemon until the lock comes free.\",\n \"`runpane loops list|create|update|run|pause|resume|delete` manage loops (scheduled tasks): a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, agents use their harness's own sleep and wake. See [LOOPS.md](LOOPS.md).\",\n \"`runpane agents start|status|send` finish the three common agent jobs in one call each: start an agent on a task (a new tab in the work's Pane with `--pane <id>`, or a new Pane for new work), check on it, and send it a follow-up.\",\n \"`runpane report --state ready|blocked|failed|done` is how a worker hands back its result. It stores the latest report on the worker's panel (state, `--pr`, `--head`, up to 16,000 characters of `--summary` or `--summary-file`, and the `--question` a blocked worker needs answered), journals an opt-in `agent.report` watch event (`REPORT <pane-name> pane <pane-id> panel <panel-id> ready pr#747 fc5dce9`; it skips the `--min-interval` batch), records it as Session activity, and shows it in `agents status`, `panels list`, and `sessions overview` (each panel's own report as `panes[].panels[].report`, with a JSON object summary as `data`, and the Pane's newest as `panes[].report`). Inside a Pane terminal the panel comes from `PANE_SESSION_ID` and `PANE_PANEL_ID`; elsewhere pass `--pane` and `--panel`. It prints how many watches were waiting for the report and received it (`Delivered to 1 waiting watcher.`, `liveWatchers` in JSON). Zero means nobody was waiting right now, not that the report is lost: it stays on the panel, and a named-cursor watch receives it when it next waits.\",\n \"`runpane notify` asks the person a question, or tells them something, on screen wherever they are, and prints their answer: `--ui options` with 1 to 9 `--choices` returns `{answer, note?}` (the chosen label), or with 2 to 4 `--questions` (`[{title, choices}]`) one pick each, as `{answer, answers}`, `--ui approve` returns `{approved, note?}`, `--ui ask` returns `{answer}`, and `--ui info` returns `{delivered: \\\"screen\\\"|\\\"inbox\\\"}` at once. Any ui takes up to 6 `--links` (`{label, path}`, `{label, url}` or `{label, approval}` for another approval), shown as chips on the card. When the person types a reply it returns `{reply, picks?, id, next}` (or, with no open call, types a `reply:` line into the asking tab): the reply may answer the question or ask one. `runpane notify --took <id|Q-n> answer` records it as the answer; `--took <id|Q-n> question [--status <text>]` shows that the agent is getting more context, and `runpane notify --update <id|Q-n> --asked <text> --what-changed <text>` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` saves a new version and waits on it. `--took` prints `{took, id, next?}`. Only the reply's reader (the asker, the orchestrator that relayed it, or Autopilot) may run them. The call waits with no time limit. A `{waiting: true, id, next}` result means the wait should continue with `runpane notify --resume <id>`; `runpane mcp` asks for it only for clients whose tool calls time out. When Pane is not running or Notify is off it prints `{unavailable, next}` and exits 0, so the agent asks in its terminal. Invalid input fails before anything is shown, with a message naming the field. A Session orchestrator adds `--relays <item>` (an id from `runpane inbox`) to relay a worker's question: the same card gains the choices, context and links, the call waits on it as usual, and the answer also goes straight to the worker's tab. Autopilot's own item takes `--about-session <id|name>`, the Session it is about. It needs no `--yes`: asking the person is the point.\",\n \"Work sent to an agent through `runpane panels create`, `panes create` (with a prompt), or `panels submit` records that the sender waits for a `runpane report`, unless the request says `--source user` (Pane's own UI does) or `--no-expect-report`; `--expect-report` asks for one anyway. Typing into a tab never does. If the agent then goes ready or its panel exits without a report, Pane journals one opt-in `agent.report.missing` entry (`MISSING REPORT <pane-name> pane <pane-id> panel <panel-id> ready|exited`) whose `lastOutput` is the agent's last transcript reply or last screen lines; it skips the `--min-interval` batch and reaches only watchers that list it in `--kinds`. While the agent's screen shows background work (Claude's `· 1 shell` footer or a running background subagent row, Codex's `background terminal running` line), Pane waits and looks again every 10 seconds instead. `agent.blocked` entries carry `screenLines`, the prompt the agent waits on, and are journalled once a panel has stayed blocked for 2 seconds.\",\n \"The orchestrator guide prescribes submit without `--wait`, followed by its named watch; use one completion consumer. As an optional CLI route, `--wait` on `runpane panels submit`, `panels create`, and `panes create` turns the expected report on and blocks on that panel until the first `agent.report`, `agent.report.missing`, `agent.blocked`, `panel.exited`, or `pane.gone`, then prints one line of JSON (`dispatchWaitResult`): the outcome, ids, the report's state, summary, `summaryPath`, PR and head, `screenLines` for a prompt, or `lastOutput` for a missing report, never the transcript. It exits 0 for a report, 3 missing, 4 blocked, 5 exited or gone, and 124 when `--timeout <duration>` passes. It follows the journal from the position the daemon took before delivering the work, so nothing that happens right after delivery is missed. It reuses watch --until-event and returns indeterminate (exit 6) if the journal epoch changes or retained history is lost; inspect the panel and inbox before retrying. Report-expected dispatches are serialized per panel and refused before writing while a result or missing-output collection is outstanding. A send with --no-expect-report is a reply (an answer, or an addition to the current work): it is delivered while a report is outstanding and leaves that expectation in place. Then watch for the original result.\",\n \"`runpane inbox [--session <id|name>] [--json]` lists what a Session's orchestrator has not acknowledged: reports, missing reports, and on-screen questions (a question clears itself once its panel moves on), plus `answers`, what its workers were told, kept 7 days; an answer with no `decision` still needs its D-n. `runpane inbox ack <item-id>...` or `--all --session <id|name>` clears items, and `runpane inbox ack <item-id>... --decision <D-n>` records the D-n, even on an item already cleared. An orchestrator runs it when it starts or resumes, to catch anything that finished while it was down; Pane never injects the inbox into its turns.\",\n \"`runpane panels resume --panel <panel-id>` restarts a stopped terminal panel in place, keeping its panel id: an agent panel relaunches with its resume command (Claude `--resume <session>`, `codex resume <session>`, Agent Farm `agent-farm run <profile> --resume <id>` with the ID Agent Farm printed; the result's `agentSessionId` names it), the same restart the app runs when it shows the panel, without taking focus. A running panel is left alone (`action: \\\"already-running\\\"`); one whose agent exited back to its shell is stopped, and resume runs its saved command again in that shell (`action: \\\"resumed\\\"`). If that command exits at once, `--wait-ready` returns `ok: false` with `reason.code: \\\"command-exited\\\"` and the error line it printed. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope and skips paused ones, `--concurrency` at a time (default 3), then, with `--wait-ready`, waits on the resumed panels together, and returns `{ resumed, alreadyRunning, failed, notReady, items[] }` with each panel's own result, readiness, and blocker or error. After a Pane restart, or once an agent exits back to its shell, stopped panels report `running: false` (`initialized` stays true while the shell lives) in `panels list`, `panels screen`, and `panels wait` (which returns `stopped: true` instead of timing out), and `nextCommand` points at `panels resume`. `runpane watch` reports them as `panel.stopped` (`STOPPED ...`) when `--kinds` lists that kind; other consumers and `workspace state` see `agent.unknown` with `running: false`, never `agent.ready`.\",\n \"`runpane panels pause --panel <panel-id>`, `panes pause --pane <pane-id>` and `sessions pause --session <id|name>` pause a tab, a Pane, or a Session's chat and Panes: Pane stops each terminal tab the way quitting does, without asking, keeps its agent's conversation, and marks it paused. A chat tab can't pause yet: `panels pause --panel` on one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE`, and `panes pause` and `sessions pause` leave its agent running and count it in `chatTabsNotPaused`. A paused tab stays stopped through clicks, app restarts and updates until `panels resume --panel`, `panes resume` or `sessions resume` restarts it in its saved conversation. Each returns `{ action, scope, panelIds, unchanged, failed, chatTabsNotPaused, message, nextCommand }`. `panes list` reports a Pane's `paused` mark (`{ at, by: \\\"manual\\\" | \\\"auto\\\", idleHours? }`) and `inactiveSince`, the latest terminal output or input in its tabs or the last time the person selected it; `panels list` reports each tab's `paused`. `runpane watch` reports `panel.paused` (`PAUSED ...`) and `panel.unpaused` (`UNPAUSED ...`) under `--follow`, to a Session-scoped watch, or when `--kinds` lists them.\",\n \"`runpane panels last-message --panel <panel-id>` reads a Claude or Codex agent's last reply from its transcript (up to `--limit` characters, default 20,000, keeping the end and reporting `truncated`). It never scrapes the screen: without a transcript it prints `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and exits 1.\",\n \"`runpane panels conversation --conversation <id> [--source startup|resume|clear|compact]` records which conversation a terminal panel's agent is on (daemon action `runpane:panels:conversation`, request `{ panelId, paneId?, conversationId, source? }`, result `{ ok: true, paneId, panelId, conversationId, source?, sure }`; `sure` is true when the report came since the panel's agent started). It is not an `agent.report` event. The panel comes from `--panel` (with an optional `--pane`), else `PANE_PANEL_ID` and `PANE_SESSION_ID`, as for `runpane report`. `runpane panels conversation --hook` is the command for a Claude Code `SessionStart` hook: it reads the hook's JSON from stdin (`session_id`, `source`; at most 64 KiB, for at most 2 seconds), reports it for `PANE_PANEL_ID`, prints nothing on stdout or stderr, and always exits 0: outside Pane, on bad input, when Pane is not running, and with a Pane that predates the action. Without `--hook` it prints `Recorded conversation <id> for panel <panel-id>.` and fails like any other command.\",\n \"Commands with a contract `daemonAction` (the `panes` git, script, restore, and move commands, `folders list|create`, and `links open`) call the same Pane daemon channel as the matching button in the app and print `{ ok, data, error }`. Destructive ones add a pane:// `link` to review the Pane.\",\n \"`runpane links create` builds `pane://open?...` links; opening one in Pane selects what it names and never changes Pane state.\",\n \"`runpane docs search|read` search and read Pane docs, help, and installed Pane Chat skills offline. They ship in the npm package and the Pane app only.\",\n \"`runpane handoff` hands a task to another agent on this or another of your machines. The sending agent writes the note from `runpane handoff --template` (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state); `runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md` checks every section is filled in, requires the branch to be pushed (or pushes it with `--push`, never forcing), writes the note to that machine's `~/.pane/handoffs/` and starts the agent in a new Pane branched from the sender's branch. When the sender is a Pane tab running a Claude, Codex, Cursor or OpenCode session and the receiver is the same agent, the handoff carries that session: on another machine its files are copied into that machine's agent store and the receiver resumes the same id; on this machine the receiver resumes a fork, because two agents never resume one session on one machine. The receiver starts with no prompt, and the CLI runs the continuity check and reports JSON `continuity.result` `continued`, `inconclusive` (exit 0; `next` gives a probe question) or `not-continued` (exit 1; the receiver is kept and `next` gives the `--note-only` command). `--note-only`, a different agent, or a tab with no carried session starts a fresh agent with the note (`fresh-agent`). JSON `continuity` is `{ carried, result, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }`: `sessionId` is the sender's session, `resumedId` the id the receiver resumes (the fork's on this machine), `checks` are the continuity checks (`historyLoaded` is null when there was nothing to compare; `originalUntouched` is set for forks), and a dry run sets `carried`, `sessionId`, `fork`, `files` and `bytes` with no `result`. After `continued` or `inconclusive` the sending agent ends its turn and makes no more changes on the branch. An OpenCode session travels as its export: `opencode export` on this machine, then `opencode import` inside the receiving repository there, and an OpenCode receiver is accepted only to continue an OpenCode session. A Cursor session is carried only to another machine, best effort since Cursor's store is undocumented: Cursor has no fork, and a copied chat still writes to the original, so on this machine the result is `not-continued` and nothing is started. A destination's agent store is assumed at its default location (`~/.claude`, `~/.codex`, `~/.cursor`); a copy already there is replaced only when the sender's holds all of it and no agent there runs it, otherwise nothing is copied and the result is `not-continued`. A destination Pane too old to resume a carried session is refused before anything is sent (`ERR_HANDOFF_RESUME_UNSUPPORTED`). From a Session's terminal it skips git and creates a Session with the same name, goal and agent on the other machine instead; a same-agent Session resumes the sender's conversation when first opened. The receiver reports back to the sender's panel.\",\n \"`runpane pr watch`: Register durable GitHub PR monitoring for a Pane.\",\n \"`runpane pr unwatch`: Remove a Pane’s explicit PR registration.\",\n \"`runpane pr list`: List PR registrations and stopped watches.\",\n \"`runpane autopilot status|waiting|stack` read Autopilot (experimental): its switch, run and stack, and its queue: every approval or question waiting on the person on every machine it can reach, grouped by Session, plus `offline` and `cantSee` machines. `runpane autopilot stack push (--item <id>|--file <path>|--url <url>|--session <id|name>) [--title <text>] [--note <text>] [--tab <path-or-url>]... [--next]` puts one entry on its stack; relative paths resolve against the caller's directory, and a new entry never replaces the one on screen. `runpane autopilot answer <id> (--choice <label>|--approve|--deny|--text <text>) [--note <text>] --because <why>`, `runpane autopilot close <id> --reason <why>` and `runpane autopilot rewrite <id> --ui <options|approve|ask> --title <text> [--context <text>] [--choices <json>] [--suggestions <json>] [--links <json>]` act on an item, on the machine that holds it, recorded \\\"by Autopilot\\\" on every device; the person can Override an answer and reopen a close. Only Autopilot may run them (`AUTOPILOT_ONLY`), and they refuse a permission prompt with `AUTOPILOT_CANNOT_ANSWER`. An item someone else already answered is refused with who did.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes ...`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. `agent-context` and `version` accept and ignore `--pane-dir`, so one `--pane-dir` can be passed to every runpane command. In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, use Pane's own `runpane` (on PATH in Pane's WSL terminals, or `/mnt/c/Users/<you>/.pane/bin/runpane`), which reaches the Windows named-pipe daemon through WSL interop; older Pane builds need `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'`.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers emit best-effort telemetry to `https://runpane.com/api/runpane/telemetry` for command failures, download request/success/failure, and GitHub fallback usage. Routine command starts and successes emit nothing. A command failure is a thrown error, or a non-zero exit from `install` or `update` (also when setup runs them); other exit codes are documented results (such as `panels wait` not ready or `doctor` findings) and emit nothing. Polling and status commands (`panes list`, `panels wait`, `watch`, `panes archive`, `workspace state`, `agents status`, `doctor`) emit no failure events, and neither does a `not_found` failure outside `install` and `update`. A failure event carries the command, `failure_stage`, `failure_category`, `exit_code` when known, and `error_code` (the code printed in the `--json` error). Both wrappers share a per-install limit in `<PANE_DIR>/runpane-wrapper-telemetry.json`: one failure event per cause per hour and at most 10 per hour.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so installs reporting failures or downloads can be counted with `count(DISTINCT properties.install_id)`; this is not a count of all CLI users.\",\n \"Wrapper telemetry must be disabled in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `TF_BUILD`, `JENKINS_URL`, `TEAMCITY_VERSION` or `CODEBUILD_BUILD_ID` set), when `DO_NOT_TRACK=1` or `RUNPANE_TELEMETRY_DISABLED` is set, and when the local config explicitly disables analytics. It must not include raw paths, labels, prompts, raw error text, or environment values.\",\n \"Desktop local control reports useful failures as `runpane_local_control_failed`; normal polling, wait deadlines, and optional agent unavailability emit nothing. Reports contain coarse operation and failure classification only, with a five-minute deduplication interval and a maximum of 20 per hour per app process. Handled CLI failures are ordinary events, not exceptions.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"doctor\",\n \"--report\",\n \"--title\",\n \"watch failed\",\n \"--body-file\",\n \"/tmp/watch-evidence.txt\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"monitor\",\n \"--from\",\n \"earliest\",\n \"--follow\",\n \"--heartbeat\",\n \"60s\",\n \"--idle-after\",\n \"600000\",\n \"--ready-settle\",\n \"180000\",\n \"--blocked-settle\",\n \"30000\",\n \"--min-interval\",\n \"600000\",\n \"--expire-after\",\n \"10800000\",\n \"--exclude-pane\",\n \"pane-old\",\n \"--kinds\",\n \"agent.ready,agent.idle\",\n \"--include-held-input\",\n \"--self-test\"\n ],\n [\n \"daemon\",\n \"repair\",\n \"--pane-dir\",\n \"/tmp/pane-remote\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"cost\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--pinned\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"pin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"unpin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"rename\",\n \"--pane\",\n \"session-1\",\n \"--name\",\n \"issue-393\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"focus\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"user\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"list\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"get\",\n \"--session\",\n \"demo\",\n \"--json\"\n ],\n [\n \"sessions\",\n \"update\",\n \"--session\",\n \"demo\",\n \"--from-json\",\n \"-\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel=panel-1\",\n \"--text=- [ ] item\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"commit\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"fix: title\\n\\nbody\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"links\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--panel\",\n \"panel-1\",\n \"--json\"\n ],\n [\n \"links\",\n \"open\",\n \"--url=pane://open?pane=pane-1\",\n \"--yes\"\n ],\n [\n \"mcp\",\n \"--toolsets\",\n \"core,git\",\n \"--read-only\"\n ],\n [\n \"docs\",\n \"search\",\n \"--query\",\n \"archive a pane\",\n \"--limit\",\n \"3\"\n ],\n [\n \"docs\",\n \"read\",\n \"--doc\",\n \"docs/PANE_MCP.md\"\n ],\n [\n \"agents\",\n \"start\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"fix-login\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Fix the login redirect\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"agents\",\n \"send\",\n \"--pane\",\n \"pane-1\",\n \"--text=- [ ] item\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--keys\",\n \"down,enter\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"squash-rebase\",\n \"--pane\",\n \"pane-1\",\n \"--message\",\n \"feat: land it\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"folders\",\n \"create\",\n \"--repo\",\n \"3\",\n \"--name\",\n \"Reviews\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"move\",\n \"--pane\",\n \"pane-1\",\n \"--folder\",\n \"folder-9\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"watch\",\n \"--follow\"\n ],\n [\n \"watch\",\n \"--session\",\n \"__orchestration_session_1b4e28ba-2fa1-11d2-883f-0016d3cca427__\",\n \"--follow\",\n \"--json\",\n \"--kinds\",\n \"agent.ready,agent.blocked,pane.associated,pane.detached,pr.conflicted,pr.checks,pr.merged\",\n \"--ready-settle\",\n \"180000\"\n ],\n [\n \"watch\",\n \"--session=Release train\",\n \"--as\",\n \"release-watch\",\n \"--exclude-pane\",\n \"pane-old\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\",\n \"--pane-dir=/tmp/pane\"\n ],\n [\n \"version\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--agent\",\n \"claude\",\n \"--base-branch\",\n \"origin/release\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--tool-command\",\n \"wrapper codex-run\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"farm\",\n \"--tool-command\",\n \"agent-farm run free-range\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Plan\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"long\",\n \"--agent\",\n \"claude\",\n \"--initial-input-file\",\n \"brief.md\",\n \"--as-file-pointer\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"ready\",\n \"--pr\",\n \"747\",\n \"--head\",\n \"FC5DCE9\",\n \"--summary\",\n \"Done: tests pass\",\n \"--json\"\n ],\n [\n \"report\",\n \"--state\",\n \"blocked\",\n \"--question\",\n \"Which API version?\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--summary-file\",\n \"-\"\n ],\n [\n \"report\",\n \"--state=done\",\n \"--summary-file=/tmp/report.md\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--all-stopped\",\n \"--wait-ready\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"last-message\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"5000\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation\",\n \"0b6c5f0e-8d1a-4f43-9a51-2f7d0c1e9b44\",\n \"--source\",\n \"resume\",\n \"--panel\",\n \"panel-1\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--conversation=abc\",\n \"--source=compact\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"conversation\",\n \"--hook\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5a\",\n \"--base\",\n \"release/foo\",\n \"--branch\",\n \"agents/w5a\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"prompt.md\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"w5b\",\n \"--base-branch\",\n \"origin/main\",\n \"--branch\",\n \"agents/w5b\",\n \"--worktree-name\",\n \"w5b-dir\",\n \"--tool-command\",\n \"bash\",\n \"--initial-input-file\",\n \"-\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--remove-worktree\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"refactor\",\n \"--merged\",\n \"--remove-worktree\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"testing-account\",\n \"--ttl\",\n \"30m\",\n \"--wait\",\n \"1800000\",\n \"--note\",\n \"call QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"staging-db\",\n \"--ttl\",\n \"1500\",\n \"--pane\",\n \"session-1\",\n \"--panel\",\n \"panel-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name=db\",\n \"--ttl=2h\"\n ],\n [\n \"lock\",\n \"release\",\n \"--name\",\n \"testing-account\",\n \"--force\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"list\",\n \"--session\",\n \"Release QA\",\n \"--json\"\n ],\n [\n \"lock\",\n \"--help\"\n ],\n [\n \"panels\",\n \"open\",\n \"--pane\",\n \"session-1\",\n \"--file\",\n \"plan.html\",\n \"--title\",\n \"Plan\",\n \"--split\",\n \"--no-focus\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"open\",\n \"--url\",\n \"http://localhost:3000\",\n \"--tab\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"correction\",\n \"--interrupt\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"repos\",\n \"get\",\n \"--repo\",\n \"Pane\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--build-script\",\n \"pnpm install\",\n \"--run-script\",\n \"pnpm dev\",\n \"--worktree-folder\",\n \"../wt\",\n \"--clear\",\n \"archive-script,system-prompt\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--as\",\n \"orch\",\n \"--until-event\",\n \"--json\",\n \"--kinds\",\n \"agent.report,agent.blocked,panel.exited,pane.gone\",\n \"--timeout-ms\",\n \"600000\"\n ],\n [\n \"pr\",\n \"watch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"unwatch\",\n \"--pane\",\n \"worker\",\n \"--url\",\n \"https://github.com/acme/app/pull/7\"\n ],\n [\n \"pr\",\n \"list\",\n \"--pane\",\n \"worker\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"0\"\n ],\n [\n \"watch\",\n \"--until-event\",\n \"--timeout-ms\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--timeout-ms\",\n \"off\"\n ],\n [\n \"watch\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--expire-after\",\n \"off\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--ready-settle\",\n \"5s\",\n \"--blocked-settle\",\n \"2s\",\n \"--min-interval\",\n \"1m\",\n \"--idle-after\",\n \"20m\",\n \"--expire-after\",\n \"off\",\n \"--heartbeat\",\n \"30s\"\n ],\n [\n \"watch\",\n \"--follow\",\n \"--since\",\n \"42\",\n \"--epoch\",\n \"test-epoch\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"go\",\n \"--expect-report\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"claude\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"codex\",\n \"--expect-report\",\n \"--yes\"\n ],\n [\n \"inbox\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"in-5e6f7a8b\",\n \"--session\",\n \"Release\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"--all\",\n \"--session\",\n \"Release\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"fyi\",\n \"--no-expect-report\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--input-file\",\n \"prompt.md\",\n \"--as-file-pointer\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Red\",\n \"--wait\",\n \"--timeout\",\n \"30m\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"feat\",\n \"--agent\",\n \"claude\",\n \"--prompt-file\",\n \"task.md\",\n \"--wait\",\n \"--yes\"\n ],\n [\n \"lock\",\n \"acquire\",\n \"--name\",\n \"deploy\",\n \"--wait\",\n \"5000\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"nightly\",\n \"--cron\",\n \"0 3 * * *\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Sweep bugs\",\n \"--worktree-name\",\n \"nightly-sweep\",\n \"--paused\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"create\",\n \"--repo=2\",\n \"--name=deps\",\n \"--cron=0 9 * * 1\",\n \"--tool-command\",\n \"./scripts/nightly-report.sh\",\n \"--prompt-file\",\n \"-\"\n ],\n [\n \"loops\",\n \"create\",\n \"--session\",\n \"new\",\n \"--name\",\n \"triage\",\n \"--cron\",\n \"0 9 * * 1-5\",\n \"--agent\",\n \"claude\",\n \"--prompt\",\n \"Triage\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--cron\",\n \"0 4 * * *\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"update\",\n \"--loop\",\n \"loop-1\",\n \"--session\",\n \"Nightly\",\n \"--yes\"\n ],\n [\n \"loops\",\n \"run\",\n \"--loop=loop-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"loops\",\n \"pause\",\n \"--loop\",\n \"loop-1\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"loops\",\n \"list\",\n \"--json\"\n ],\n [\n \"loops\",\n \"--help\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Ship it?\",\n \"--context\",\n \"Tests pass.\",\n \"--choices=[{\\\"label\\\":\\\"Yes\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"No\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--ui=approve\",\n \"--title=Drop the table?\",\n \"--detail=DROP TABLE t;\",\n \"--detail-label=Show the SQL\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which branch?\",\n \"--suggestions=[\\\"main\\\"]\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"info\",\n \"--title\",\n \"Done\",\n \"--action={\\\"label\\\":\\\"Open\\\",\\\"url\\\":\\\"https://example.com\\\"}\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"approve\",\n \"--title\",\n \"Ship the notes?\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR\\\",\\\"url\\\":\\\"https://example.com/pr/1\\\"}]\"\n ],\n [\n \"notify\",\n \"--resume\",\n \"n-1\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"list\",\n \"--project\",\n \"stripe\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"set\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\"\n ],\n [\n \"secrets\",\n \"delete\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"stripe\",\n \"--only\",\n \"A,B\",\n \"--env\",\n \"KEY=STRIPE_SECRET_KEY\",\n \"--env=TOKEN=GH_TOKEN\",\n \"--file\",\n \"CERT\",\n \"--\",\n \"node\",\n \"-e\",\n \"run()\",\n \"--json\"\n ],\n [\n \"secrets\",\n \"exec\",\n \"--project\",\n \"qa\",\n \"sh\",\n \"-c\",\n \"echo ok\"\n ],\n [\n \"secrets\",\n \"request\",\n \"--project\",\n \"stripe\",\n \"--name\",\n \"STRIPE_SECRET_KEY\",\n \"--title\",\n \"Stripe test key\",\n \"--reason\",\n \"- for the webhook tests\",\n \"--json\"\n ],\n [\n \"repos\",\n \"update\",\n \"--repo\",\n \"Pane\",\n \"--env\",\n \"[{\\\"key\\\":\\\"API_URL\\\",\\\"source\\\":\\\"plain\\\",\\\"value\\\":\\\"x\\\"}]\",\n \"--env-remove\",\n \"OLD,STALE\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panels\",\n \"close\",\n \"--panel\",\n \"panel-1\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--session\",\n \"session-a\",\n \"--merged\",\n \"--closed\",\n \"--dry-run\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"status\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"waiting\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--item\",\n \"n-1\",\n \"--note\",\n \"Needs a go\",\n \"--next\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--file\",\n \"/tmp/plan.html\",\n \"--title\",\n \"Plan\",\n \"--tab\",\n \"https://localhost:5173\",\n \"--tab=/tmp/notes.md\"\n ],\n [\n \"autopilot\",\n \"stack\",\n \"push\",\n \"--session\",\n \"Release\"\n ],\n [\n \"watch\",\n \"--as\",\n \"autopilot\",\n \"--kinds\",\n \"item.created,item.answered,session.created,session.archived,orchestrator.turn_ended\",\n \"--until-event\",\n \"--json\"\n ],\n [\n \"inbox\",\n \"ack\",\n \"in-1a2b3c4d\",\n \"--decision\",\n \"D-147\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--relays\",\n \"in-1a2b3c4d\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Q-39: Run it?\"\n ],\n [\n \"notify\",\n \"--ui\",\n \"ask\",\n \"--title\",\n \"Which?\",\n \"--about-session=Release\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--choice\",\n \"Yes\",\n \"--note\",\n \"Go\",\n \"--because\",\n \"D-22\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--approve\",\n \"--because=The person said so\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--deny\",\n \"--because\",\n \"D-23\"\n ],\n [\n \"autopilot\",\n \"answer\",\n \"n-1\",\n \"--text\",\n \"main\",\n \"--because\",\n \"D-24\"\n ],\n [\n \"autopilot\",\n \"close\",\n \"n-1\",\n \"--reason\",\n \"The PR merged\",\n \"--json\"\n ],\n [\n \"autopilot\",\n \"rewrite\",\n \"n-1\",\n \"--ui\",\n \"options\",\n \"--title\",\n \"Q-3: Which?\",\n \"--context\",\n \"Why\",\n \"--choices=[{\\\"label\\\":\\\"A\\\"}]\",\n \"--links=[{\\\"label\\\":\\\"Notes\\\",\\\"url\\\":\\\"https://example.com\\\"}]\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"answer\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took=n-1\",\n \"question\",\n \"--status\",\n \"Checking the logs\",\n \"--json\"\n ],\n [\n \"notify\",\n \"--took\",\n \"Q-20\",\n \"--took-as\",\n \"answer\"\n ],\n [\n \"notify\",\n \"--update\",\n \"Q-20\",\n \"--asked\",\n \"Can it be undone?\",\n \"--what-changed\",\n \"- Added: it can be undone\",\n \"--questions\",\n \"[{\\\"title\\\":\\\"A\\\",\\\"choices\\\":[{\\\"label\\\":\\\"x\\\"}]},{\\\"title\\\":\\\"B\\\",\\\"choices\\\":[{\\\"label\\\":\\\"y\\\"}]}]\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane repos rename\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\",\n \"runpane loops list\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\",\n \"python -m runpane loops list\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"description\": \"A failed command under --json, printed on stdout with a non-zero exit. The same message, plus a Next line, goes to stderr.\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\",\n \"description\": \"What happened, why, and what was or was not changed.\"\n },\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable identifier to branch on, such as ENOENT or ERR_UNKNOWN_CHANNEL. ERR_RUNPANE_FAILED when the failure has no specific code.\"\n },\n \"next\": {\n \"type\": \"string\",\n \"description\": \"The step to take next, when one is known.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteDaemonService\",\n \"remoteSetup\",\n \"watchDefaults\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteDaemonService\": {\n \"type\": \"object\",\n \"required\": [\n \"paneDir\",\n \"managed\",\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"managed\": {\n \"type\": \"boolean\"\n },\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"executableHealth\": {\n \"type\": \"object\",\n \"required\": [\n \"processImage\",\n \"restart\",\n \"checkedAt\"\n ],\n \"properties\": {\n \"processImage\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"runtimePath\",\n \"installedPath\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"current\",\n \"replaced\",\n \"deleted\",\n \"unknown\"\n ]\n },\n \"runtimePath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"installedPath\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"restart\": {\n \"type\": \"object\",\n \"required\": [\n \"status\",\n \"evidence\"\n ],\n \"properties\": {\n \"status\": {\n \"enum\": [\n \"ready\",\n \"broken\",\n \"unknown\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"resolvedPath\": {\n \"type\": \"string\"\n },\n \"evidence\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"diagnosticCode\": {\n \"enum\": [\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n },\n \"checkedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\",\n \"PANE_REMOTE_DAEMON_EXECUTABLE_DELETED\",\n \"PANE_REMOTE_DAEMON_UPDATE_PENDING\",\n \"PANE_REMOTE_DAEMON_LAUNCHER_STALE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"watchDefaults\": {\n \"type\": \"object\",\n \"required\": [\n \"follow\",\n \"untilEvent\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"follow\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"summary\",\n \"kinds\",\n \"readySettle\",\n \"blockedSettle\",\n \"minInterval\",\n \"idleAfter\",\n \"idleBackoff\",\n \"expireAfter\",\n \"heartbeat\",\n \"includeShells\",\n \"heldInputDetection\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"readySettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"blockedSettle\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"minInterval\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"expireAfter\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"heartbeat\": {\n \"type\": \"string\",\n \"pattern\": \"^(off|[0-9]+(ms|s|m|h))$\"\n },\n \"includeShells\": {\n \"type\": \"boolean\"\n },\n \"heldInputDetection\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"untilEvent\": {\n \"type\": \"object\",\n \"required\": [\n \"summary\",\n \"kinds\"\n ],\n \"properties\": {\n \"summary\": {\n \"type\": \"string\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n },\n \"cliInstalls\": {\n \"type\": \"object\",\n \"required\": [\n \"installs\",\n \"warnings\"\n ],\n \"properties\": {\n \"managedVersion\": {\n \"type\": \"string\"\n },\n \"installs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"origin\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"origin\": {\n \"enum\": [\n \"pane\",\n \"npm\",\n \"other\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"npmPrefix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"path\",\n \"message\",\n \"fix\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"RUNPANE_STALE_INSTALL\",\n \"RUNPANE_STALE_UNMANAGED\",\n \"RUNPANE_STALE_MANAGED\",\n \"RUNPANE_MANAGED_BEHIND\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"fixed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"ok\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"workspacesSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"machine\",\n \"otherMachines\",\n \"lines\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"This machine's Tailscale name.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"description\": \"Why workspaces are off.\"\n },\n \"fix\": {\n \"type\": \"string\",\n \"description\": \"The one step that turns them on.\"\n },\n \"otherMachines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"os\",\n \"online\",\n \"owner\",\n \"mine\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own Tailscale login.\"\n }\n }\n }\n },\n \"lines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The block printed by help, doctor, and agent-context.\"\n }\n }\n },\n \"daemonRepairResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"paneDir\",\n \"strategy\",\n \"launcherPath\",\n \"before\",\n \"after\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"systemd-user\",\n \"launch-agent\",\n \"scheduled-task\",\n \"manual\",\n \"skipped\"\n ]\n },\n \"launcherPath\": {\n \"type\": \"string\"\n },\n \"before\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"after\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"repoGetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false,\n \"$defs\": {\n \"scriptSource\": {\n \"type\": \"object\",\n \"required\": [\n \"command\",\n \"source\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"scriptSetting\": {\n \"type\": \"object\",\n \"required\": [\n \"value\",\n \"fallback\",\n \"effective\"\n ],\n \"properties\": {\n \"value\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"fallback\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"effective\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSource\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"settings\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"currentBranch\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"configFile\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"envVars\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"currentBranch\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"configFile\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"buildScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"runScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"archiveScript\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/scriptSetting\"\n },\n \"envVars\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"repoUpdateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"repo\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\"\n }\n ]\n },\n \"settings\": {\n \"type\": \"object\",\n \"minProperties\": 1,\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"buildScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"runScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"archiveScript\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"worktreeFolder\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"openIdeCommand\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"systemPrompt\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"envVars\": {\n \"type\": \"object\",\n \"description\": \"set adds or replaces variables by key; remove deletes them by key.\",\n \"minProperties\": 1,\n \"properties\": {\n \"set\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n },\n \"remove\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoUpdateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"changed\",\n \"repo\",\n \"settings\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"changed\": {\n \"type\": \"array\",\n \"items\": {\n \"enum\": [\n \"name\",\n \"buildScript\",\n \"runScript\",\n \"archiveScript\",\n \"worktreeFolder\",\n \"openIdeCommand\",\n \"systemPrompt\",\n \"envVars\"\n ]\n }\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"settings\": {\n \"$ref\": \"#/jsonSchemas/repoGetResult/$defs/settings\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"name\"\n ],\n \"properties\": {\n \"repo\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"repoRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretSummary\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"updatedAt\",\n \"machine\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"type\": \"string\",\n \"description\": \"ISO time it was last set.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine where it was last set.\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"secrets\",\n \"storage\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"secrets\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"storage\": {\n \"type\": \"object\",\n \"description\": \"Where this machine keeps values. encrypted is false only for the file store on a machine without an OS keyring.\",\n \"required\": [\n \"kind\",\n \"encrypted\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"keychain\",\n \"file\"\n ]\n },\n \"encrypted\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetRequest\": {\n \"description\": \"What `runpane secrets set` sends on runpane:secrets:set. value comes from stdin.\",\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\",\n \"value\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"value\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretSetResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"replaced\",\n \"secret\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"replaced\": {\n \"type\": \"boolean\"\n },\n \"secret\": {\n \"$ref\": \"#/jsonSchemas/secretSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"secretDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"object\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"projectEnvVar\": {\n \"description\": \"A project environment variable. Plain values are shown in full; keychain and Pane secret variables are references, never values.\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"key\",\n \"source\",\n \"value\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"plain\"\n },\n \"value\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"An existing macOS keychain generic-password item.\",\n \"required\": [\n \"key\",\n \"source\",\n \"service\",\n \"account\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"keychain\"\n },\n \"service\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"account\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The Pane secret project/name.\",\n \"required\": [\n \"key\",\n \"source\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"key\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"source\": {\n \"const\": \"secret\"\n },\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"branch\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\",\n \"cursor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"initialInputAsFilePointer\": {\n \"type\": \"boolean\"\n },\n \"initialInputAttachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"associateSession\": {\n \"type\": \"string\",\n \"description\": \"Session id to associate each created Pane with; the CLI sets it from PANE_ORCHESTRATION_SESSION_ID unless --no-associate.\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"staged\": {\n \"type\": \"boolean\"\n },\n \"attempts\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"association\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"ok\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"setupQueue\": {\n \"type\": \"object\",\n \"required\": [\n \"position\",\n \"message\"\n ],\n \"properties\": {\n \"position\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"required\": [\n \"ref\",\n \"refresh\"\n ],\n \"properties\": {\n \"ref\": {\n \"type\": \"string\"\n },\n \"commit\": {\n \"type\": \"string\"\n },\n \"refresh\": {\n \"type\": \"string\",\n \"enum\": [\n \"fetched\",\n \"recent\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"notice\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\",\n \"pinned\",\n \"agentStatus\",\n \"ownership\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"inactiveSince\": {\n \"type\": \"string\",\n \"description\": \"When the Pane was last used: the latest terminal output or input in its tabs, or the last time the person selected it (ISO 8601).\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\",\n \"description\": \"Set while every terminal tab of the Pane is paused: the latest tab's mark.\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"agentStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"agentState\": {\n \"enum\": [\n \"ready\",\n \"working\",\n \"blocked\",\n \"none\"\n ]\n },\n \"ownership\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostResult\": {\n \"$defs\": {\n \"usageTotals\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"usageByModel\": {\n \"type\": \"object\",\n \"required\": [\n \"model\",\n \"provider\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\"\n ],\n \"properties\": {\n \"model\": {\n \"type\": \"string\"\n },\n \"provider\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneCostSlice\": {\n \"type\": \"object\",\n \"required\": [\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"pane\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"paneName\",\n \"worktreePath\",\n \"repoId\",\n \"archived\",\n \"createdAtMs\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheReadTokens\",\n \"cacheCreationTokens\",\n \"totalTokens\",\n \"messageCount\",\n \"estimatedCostUsd\",\n \"costIncomplete\",\n \"cacheSavingsUsd\",\n \"uncachedCostUsd\",\n \"uncachedInputTokens\",\n \"cacheHitRate\",\n \"byModel\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"archived\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"inputTokens\": {\n \"type\": \"number\"\n },\n \"outputTokens\": {\n \"type\": \"number\"\n },\n \"cacheReadTokens\": {\n \"type\": \"number\"\n },\n \"cacheCreationTokens\": {\n \"type\": \"number\"\n },\n \"totalTokens\": {\n \"type\": \"number\"\n },\n \"messageCount\": {\n \"type\": \"number\"\n },\n \"estimatedCostUsd\": {\n \"type\": \"number\"\n },\n \"costIncomplete\": {\n \"type\": \"boolean\"\n },\n \"cacheSavingsUsd\": {\n \"type\": \"number\"\n },\n \"uncachedCostUsd\": {\n \"type\": \"number\"\n },\n \"uncachedInputTokens\": {\n \"type\": \"number\"\n },\n \"cacheHitRate\": {\n \"type\": \"number\"\n },\n \"byModel\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/usageByModel\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"fromMs\",\n \"toMs\",\n \"pricingAsOf\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"fromMs\": {\n \"type\": \"number\"\n },\n \"toMs\": {\n \"type\": \"number\"\n },\n \"pricingAsOf\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/pane\"\n }\n },\n \"unattributed\": {\n \"$ref\": \"#/$defs/paneCostSlice\"\n },\n \"totals\": {\n \"$ref\": \"#/$defs/usageTotals\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"gen\",\n \"at\",\n \"kind\",\n \"paneId\",\n \"paneName\",\n \"source\"\n ],\n \"properties\": {\n \"gen\": {\n \"type\": \"number\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"agent.ready\",\n \"agent.busy\",\n \"agent.blocked\",\n \"agent.unknown\",\n \"agent.idle\",\n \"pane.created\",\n \"pane.gone\",\n \"panel.exited\",\n \"panel.stopped\",\n \"panel.paused\",\n \"panel.unpaused\",\n \"agent.report\",\n \"agent.report.missing\",\n \"pane.associated\",\n \"pane.detached\",\n \"pr.conflicted\",\n \"pr.checks\",\n \"pr.merged\",\n \"pr.closed\",\n \"pr.head\",\n \"pr.base_changed\",\n \"pr.base_deleted\",\n \"pr.review\",\n \"pr.comment\",\n \"pr.stopped\",\n \"github.unavailable\",\n \"github.recovered\",\n \"item.created\",\n \"item.answered\",\n \"session.created\",\n \"session.archived\",\n \"orchestrator.turn_ended\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"to\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"source\": {\n \"enum\": [\n \"agent\",\n \"exit\",\n \"session\",\n \"github\"\n ]\n },\n \"reason\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"settledMs\": {\n \"type\": \"number\"\n },\n \"idleMs\": {\n \"type\": \"number\"\n },\n \"idleCount\": {\n \"type\": \"number\"\n },\n \"heldInput\": {\n \"type\": \"string\"\n },\n \"heldInputPresent\": {\n \"type\": \"boolean\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Baseline agent entries: whether the panel's terminal process is live. A stopped panel is never agent.ready.\"\n },\n \"baseline\": {\n \"const\": true\n },\n \"replay\": {\n \"const\": true,\n \"description\": \"Set on baseline entries delivered after a reset. A replayed entry restates current state and is never a new transition: a replayed agent.ready is not READY.\"\n },\n \"changedWhileAway\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Named Session of a pane.associated or pane.detached entry.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"description\": \"PR of a pr.conflicted, pr.checks, or pr.merged entry.\",\n \"required\": [\n \"number\",\n \"url\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"base\": {\n \"type\": \"object\",\n \"description\": \"Base branch of a pr.base_changed (old to new) or pr.base_deleted (to is the PR's current base name) entry.\",\n \"required\": [\n \"from\",\n \"to\"\n ],\n \"properties\": {\n \"from\": {\n \"type\": \"string\"\n },\n \"to\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"checks\": {\n \"enum\": [\n \"passed\",\n \"failed\"\n ],\n \"description\": \"Failures are immediate; success requires complete nonempty required checks on the pinned head.\"\n },\n \"failingChecks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Newly failing check names on this head.\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\",\n \"description\": \"Report of an agent.report entry; its summary is cut to 2,000 characters (the panel keeps up to 16,000).\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Added to watch --json NDJSON events. Persist with gen after processing for numeric rearm.\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentState\": {\n \"enum\": [\n \"blocked\",\n \"working\",\n \"idle\",\n \"unknown\"\n ]\n },\n \"running\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"lastOutput\": {\n \"type\": \"string\",\n \"description\": \"What an agent.report.missing entry's agent said last, up to 2,000 characters (the end is kept).\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ],\n \"description\": \"transcript: the agent's last reply; screen: the last lines on its screen.\"\n },\n \"expectedSince\": {\n \"type\": \"string\",\n \"description\": \"When the work an agent.report.missing entry is about was sent.\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The screen lines of the prompt an agent.blocked entry's agent waits on (at most 20).\"\n },\n \"itemId\": {\n \"type\": \"string\",\n \"description\": \"The Notify item of an item.created or item.answered entry; `runpane autopilot stack push --item <id>` takes it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentReport\": {\n \"type\": \"object\",\n \"description\": \"The latest runpane report from an agent panel.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelReport\": {\n \"type\": \"object\",\n \"description\": \"A panel's latest runpane report in sessions overview. data is the summary when it is a complete JSON object, returned as the worker gave it; a plain-text, array, scalar, invalid, more than 64 levels deep, or truncated summary has no data.\",\n \"required\": [\n \"state\",\n \"reportedAt\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n },\n \"reportedAt\": {\n \"type\": \"string\"\n },\n \"data\": {\n \"type\": \"object\",\n \"description\": \"The worker's JSON object summary, returned as given. Pane never reads, renames or validates its fields.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"since\": {\n \"type\": \"number\"\n },\n \"as\": {\n \"type\": \"string\"\n },\n \"from\": {\n \"enum\": [\n \"now\",\n \"earliest\"\n ]\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"kinds\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry/properties/kind\"\n }\n },\n \"paneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Named Session id or exact name. Limits the wait to the Session's associated Panes, resolved on every read, and implies the opt-in pane.associated, pane.detached, and pr.* entries. Cannot be combined with paneIds.\"\n },\n \"excludePaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"nameContains\": {\n \"type\": \"string\"\n },\n \"agentsOnly\": {\n \"type\": \"boolean\"\n },\n \"ackNow\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInput\": {\n \"type\": \"boolean\"\n },\n \"includeHeldInputPresence\": {\n \"type\": \"boolean\"\n },\n \"idleAfterMs\": {\n \"type\": \"number\"\n },\n \"idleWindowStartMs\": {\n \"type\": \"number\"\n },\n \"settleMs\": {\n \"type\": \"number\"\n },\n \"blockedSettleMs\": {\n \"type\": \"number\"\n },\n \"minIntervalMs\": {\n \"type\": \"number\"\n },\n \"idleBackoff\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Expected epoch for since. Requires since and cannot be combined with as. Mismatch returns epoch-changed reset.\"\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceStateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"workspaceWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"epoch\",\n \"generation\",\n \"entries\",\n \"timedOut\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"epoch\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/workspaceEntry\"\n }\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"dropped\": {\n \"type\": \"number\"\n },\n \"reset\": {\n \"type\": \"object\",\n \"required\": [\n \"reason\"\n ],\n \"properties\": {\n \"reason\": {\n \"enum\": [\n \"first-use\",\n \"epoch-changed\",\n \"cursor-truncated\",\n \"unknown-consumer\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"session\": {\n \"type\": \"object\",\n \"description\": \"The Session a session request resolved to. A client that sent session and gets no session back is talking to a daemon that ignored it.\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"activityGeneration\": {\n \"type\": \"number\",\n \"description\": \"Latest meaningful activity in scope, including events hidden by output kinds or cadence. Natural expiry resets when it advances.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"favoritePinnedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneRenameResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"pane\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"pane\": {\n \"$ref\": \"#/jsonSchemas/paneListResult/properties/panes/items\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneFocusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"focused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"focused\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ],\n \"description\": \"`completed`: the worktree is gone from its path and from git (see trashDeletion). `failed`: removal failed and the worktree may remain; ok is false. `timeout`: the archive script or git removal is still running in the background; ok stays true. `not-applicable`: nothing was removed (main-repo Pane, or an adopted worktree without --remove-worktree). Released CLIs decode exactly these values.\"\n },\n \"trashDeletion\": {\n \"enum\": [\n \"pending\",\n \"done\"\n ],\n \"description\": \"Present with worktreeCleanup `completed`. `pending`: the removed worktree was moved into the repository's .git/pane-trash and its files are still being deleted in the background. `done`: the files are gone.\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"upstreamGone\": {\n \"type\": \"boolean\",\n \"description\": \"The branch had an upstream that no longer exists on the remote, so commits were compared with the base branch.\"\n },\n \"mergedViaPr\": {\n \"type\": \"object\",\n \"description\": \"A merged pull request whose head commit is HEAD. Its commits are not counted as unpushed.\",\n \"required\": [\n \"number\",\n \"headOid\"\n ],\n \"properties\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"headOid\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"busyAgents\": {\n \"type\": \"array\",\n \"description\": \"Agents in the Pane that are mid-turn (working) or waiting for an answer (blocked). Archiving would stop them, so archive refuses with agent-busy unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"state\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"enum\": [\n \"working\",\n \"blocked\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"unsentInput\": {\n \"type\": \"array\",\n \"description\": \"Tabs holding input a person typed that archiving would lose: an agent composer's unsent text or an editor's unsaved edits. Archive refuses with unsent-input unless --force.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"title\",\n \"kind\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"composer\",\n \"editor\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"dryRun\",\n \"wouldArchive\",\n \"forced\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"wouldArchive\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/2/properties/blocked\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"upstream\": {\n \"type\": \"string\"\n },\n \"upstreamRefreshed\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n },\n \"unpushedCommitDetails\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"sha\",\n \"subject\"\n ],\n \"properties\": {\n \"sha\": {\n \"type\": \"string\"\n },\n \"subject\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reason\": {\n \"enum\": [\n \"external-worktree\",\n \"main-repo\",\n \"missing-project-context\",\n \"git-error\"\n ]\n },\n \"worktreeWillRemain\": {\n \"const\": true\n },\n \"busyAgents\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/busyAgents\"\n },\n \"unsentInput\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck/properties/unsentInput\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"dock\": {\n \"type\": \"boolean\",\n \"description\": \"True on the Pane's terminal dock (its plain shell); omitted on every other panel.\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"resumeId\": {\n \"type\": \"string\"\n },\n \"resumeSource\": {\n \"enum\": [\n \"agent-farm\",\n \"configured\",\n \"agent\"\n ]\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n },\n \"openedBy\": {\n \"type\": \"object\",\n \"description\": \"Who opened this tab through runpane, and the tab that opened it when known. Omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose source is agent.\",\n \"required\": [\n \"source\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"agent\",\n \"user\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n },\n \"workspaces\": {\n \"$ref\": \"#/jsonSchemas/workspacesSummary\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\",\n \"composer\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"composer\": {\n \"type\": \"object\",\n \"required\": [\n \"isPresent\",\n \"hasUndeliveredText\"\n ],\n \"properties\": {\n \"isPresent\": {\n \"type\": \"boolean\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"ghostText\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n },\n \"asFilePointer\": {\n \"type\": \"boolean\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"interrupt\": {\n \"type\": \"boolean\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n },\n \"operationId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"enum\": [\n \"cr\",\n \"tab\",\n \"none\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\",\n \"acp\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"type\": \"object\",\n \"required\": [\n \"state\",\n \"evidence\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"taken\",\n \"queued\",\n \"in-composer\",\n \"unknown\"\n ]\n },\n \"evidence\": {\n \"enum\": [\n \"transcript\",\n \"screen\",\n \"argv\",\n \"acp-receipt\"\n ]\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human explanation of delivery; queued means the agent has not read the message yet.\"\n }\n },\n \"additionalProperties\": false\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitComposerResult/properties/blocked\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"leading-bang-runs-shell\",\n \"leading-hash-memory\",\n \"leading-slash-command\",\n \"leading-at-mention\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n },\n \"operationId\": {\n \"type\": \"string\",\n \"description\": \"Durable host operation ID for Chat delivery reconciliation.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"stopped\": {\n \"const\": true,\n \"description\": \"The wait did not match and the panel's terminal is not running (ready and idle waits return this after a 2-second grace instead of timing out); nextCommand is `runpane panels resume`.\"\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\",\n \"acpTranscript\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"action\",\n \"message\",\n \"panel\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ],\n \"description\": \"resumed: the panel's terminal was restarted in place. already-running: nothing was restarted.\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\",\n \"description\": \"The agent conversation the panel resumes, when Pane knows it.\"\n },\n \"panel\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"agentDetection\": {\n \"enum\": [\n \"declared\",\n \"command\",\n \"process\",\n \"screen\"\n ]\n },\n \"launchCommand\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"paused\": {\n \"$ref\": \"#/jsonSchemas/pausedMark\"\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelResumeManyResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"scope\",\n \"resumed\",\n \"alreadyRunning\",\n \"failed\",\n \"notReady\",\n \"concurrency\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"Every stopped panel in scope resumed and, with --wait-ready, became ready.\"\n },\n \"scope\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\",\n \"sessionName\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"all-stopped\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"resumed\": {\n \"type\": \"number\"\n },\n \"alreadyRunning\": {\n \"type\": \"number\",\n \"description\": \"Agent panels in scope that were already running and were left alone.\"\n },\n \"failed\": {\n \"type\": \"number\",\n \"description\": \"Panels that could not be restarted; their item carries error.\"\n },\n \"notReady\": {\n \"type\": \"number\",\n \"description\": \"With --wait-ready: resumed panels that were blocked or not ready within --ready-timeout-ms.\"\n },\n \"concurrency\": {\n \"type\": \"number\",\n \"description\": \"Panels restarted at once.\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"paneName\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"action\": {\n \"enum\": [\n \"resumed\",\n \"already-running\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"agentSessionId\": {\n \"type\": \"string\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why this panel could not be resumed; the others still were.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelLastMessageResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"agentType\",\n \"text\",\n \"length\",\n \"limit\",\n \"truncated\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"enum\": [\n \"claude\",\n \"codex\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"length\": {\n \"type\": \"number\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"truncated\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"paneId\",\n \"reason\",\n \"message\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"reason\": {\n \"const\": \"transcript-unavailable\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelConversationRequest\": {\n \"type\": \"object\",\n \"description\": \"What runpane panels conversation sends the daemon (runpane:panels:conversation).\",\n \"required\": [\n \"panelId\",\n \"conversationId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"paneId\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"conversationId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 256\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelConversationResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"conversationId\",\n \"sure\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"conversationId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"startup\",\n \"resume\",\n \"clear\",\n \"compact\"\n ],\n \"description\": \"What started the conversation, as Claude Code's SessionStart hook names it.\"\n },\n \"sure\": {\n \"type\": \"boolean\",\n \"description\": \"True when this report came since the panel's agent started, so Pane knows the panel is on this conversation.\"\n },\n \"generation\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"expectReport\": {\n \"type\": \"boolean\",\n \"description\": \"true or false decides whether Pane waits for a `runpane report`; omitted, it waits unless source is user (and, for a submit, only on an agent's panel).\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"running\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the terminal process is live. A stopped panel can be restarted with `runpane panels resume`.\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"reason\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"no-agent-detected\",\n \"agent-not-ready\",\n \"command-exited\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"attachments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"expectedReport\": {\n \"type\": \"object\",\n \"required\": [\n \"since\"\n ],\n \"properties\": {\n \"since\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"The report Pane now waits for: set for work an agent sent, or with --expect-report.\"\n },\n \"epoch\": {\n \"type\": \"string\",\n \"description\": \"Epoch of the journal position captured before dispatch; pass it with generation when waiting.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"callerPanelId\": {\n \"type\": \"string\",\n \"description\": \"The calling tab ($PANE_PANEL_ID), recorded as the opener of the tabs this creates. The CLI sets it from PANE_PANEL_ID.\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelOpenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"type\",\n \"title\",\n \"placement\",\n \"active\",\n \"reused\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"enum\": [\n \"browser\",\n \"editor\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"filePath\": {\n \"type\": \"string\"\n },\n \"placement\": {\n \"enum\": [\n \"split\",\n \"tab\"\n ]\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"reused\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\",\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n \"force\": {\n \"type\": \"boolean\",\n \"description\": \"Close it as the tab's close button does, skipping every check but permanent: for a stuck tab, or one the person asked to close. A busy agent's turn and unsent input are lost.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"closed\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when the close was refused (the tab stays open); the CLI exits 1.\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\",\n \"description\": \"Omitted only when the tab was already gone.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\",\n \"description\": \"The tab's type, such as terminal, chat, browser or editor. Omitted only when the tab was already gone.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"closed\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\",\n \"description\": \"Closed with --force, skipping the checks that would have kept it.\"\n },\n \"alreadyClosed\": {\n \"type\": \"boolean\",\n \"description\": \"No tab has this id: it was closed already.\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"wouldClose\": {\n \"type\": \"boolean\",\n \"description\": \"Dry run only: whether the close would go ahead.\"\n },\n \"refused\": {\n \"type\": \"object\",\n \"description\": \"Why the tab was kept open. opened-by-user: not opened by an agent with --source agent (including tabs from before Pane recorded openers). permanent: a permanent tab. agent-busy: its agent is mid-turn or waiting for an answer. unsent-input: unsent composer text or unsaved editor edits. input-unknown: a live tab whose input Pane cannot read.\",\n \"required\": [\n \"code\",\n \"message\",\n \"next\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"opened-by-user\",\n \"permanent\",\n \"agent-busy\",\n \"unsent-input\",\n \"input-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"tab\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"tab\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"first-run-dialog\",\n \"codex-update\",\n \"agent-prompt\",\n \"submission_unverified\",\n \"composer-unknown\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"verification\": {\n \"enum\": [\n \"observed\",\n \"unverifiable\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"sessionListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessions\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"selectedSessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"sessionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"internalSessionId\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"object\",\n \"description\": \"Only when the update archived the Session: what happened to each associated Pane.\",\n \"required\": [\n \"archivedPaneIds\",\n \"keptPanes\",\n \"failedPanes\"\n ],\n \"properties\": {\n \"archivedPaneIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"keptPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"code\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"failedPanes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"name\",\n \"error\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"sessionOverviewResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"status\",\n \"panes\",\n \"activity\",\n \"refreshedAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/panelReport\"\n }\n }\n }\n }\n }\n }\n },\n \"activity\": {\n \"type\": \"array\"\n },\n \"refreshedAt\": {\n \"type\": \"string\"\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"locks\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"locks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"scope\",\n \"owner\",\n \"acquiredAt\",\n \"expiresAt\",\n \"ttlMs\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"scope\": {\n \"enum\": [\n \"session\",\n \"global\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"external\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"acquiredAt\": {\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"ttlMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ttlMs\",\n \"owner\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"ttlMs\": {\n \"type\": \"number\"\n },\n \"waitMs\": {\n \"type\": \"number\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"owner\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"label\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"lockAcquireResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"renewed\",\n \"waitedMs\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acquired\": {\n \"const\": true\n },\n \"renewed\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acquired\",\n \"timedOut\",\n \"waitedMs\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"acquired\": {\n \"const\": false\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"waitedMs\": {\n \"type\": \"number\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"lockReleaseResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"forced\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"released\": {\n \"type\": \"boolean\"\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"released\",\n \"reason\",\n \"heldBy\",\n \"expiresAt\",\n \"lock\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"released\": {\n \"const\": false\n },\n \"reason\": {\n \"const\": \"not-owner\"\n },\n \"heldBy\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items/properties/owner\"\n },\n \"expiresAt\": {\n \"type\": \"string\"\n },\n \"lock\": {\n \"$ref\": \"#/jsonSchemas/lockListResult/properties/locks/items\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"daemonActionResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"data\": {},\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"link\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"linkCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"url\",\n \"target\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"target\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"pane\",\n \"repo\",\n \"session\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"docsSearchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"query\",\n \"results\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"query\": {\n \"type\": \"string\"\n },\n \"results\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"path\",\n \"title\",\n \"kind\",\n \"excerpt\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"excerpt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"docsReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"path\",\n \"title\",\n \"kind\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"doc\",\n \"skill\",\n \"help\",\n \"command\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentTaskResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"link\",\n \"ready\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"promptDelivered\": {\n \"type\": \"boolean\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"status\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"working\",\n \"ready\",\n \"blocked\",\n \"idle\",\n \"exited\",\n \"unknown\"\n ]\n },\n \"screen\": {\n \"type\": \"string\"\n },\n \"hasUndeliveredText\": {\n \"type\": \"boolean\"\n },\n \"link\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentSendResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"delivered\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"delivery\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/delivery\"\n },\n \"blocked\": {\n \"type\": \"string\"\n },\n \"promptFile\": {\n \"type\": \"string\"\n },\n \"warnings\": {\n \"$ref\": \"#/jsonSchemas/panelSubmitResult/properties/warnings\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextUnknownCommandError\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"code\",\n \"message\",\n \"candidates\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"code\": {\n \"const\": \"unknown_command\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"candidates\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"reportResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"report\",\n \"sessionIds\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"sessionIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"liveWatchers\": {\n \"type\": \"number\",\n \"description\": \"Watches that were waiting for this report when it was journaled and received it. A snapshot: a named-cursor watch that waits later still receives it. Absent from older daemons.\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"sessionId\",\n \"merged\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"type\": \"boolean\",\n \"description\": \"Also archive Panes whose PR closed without merging, with the same unsaved-work checks.\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveBulkResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"sessionId\",\n \"merged\",\n \"removeWorktree\",\n \"archived\",\n \"skipped\",\n \"failed\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"merged\": {\n \"const\": true\n },\n \"closed\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"removeWorktree\": {\n \"type\": \"boolean\"\n },\n \"archived\": {\n \"type\": \"number\",\n \"description\": \"Panes archived, or that would be archived with --dry-run.\"\n },\n \"skipped\": {\n \"type\": \"number\"\n },\n \"failed\": {\n \"type\": \"number\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"outcome\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"outcome\": {\n \"enum\": [\n \"archived\",\n \"would-archive\",\n \"skipped\",\n \"failed\"\n ]\n },\n \"skipped\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\",\n \"agent-busy\",\n \"unsent-input\",\n \"missing-pane\",\n \"already-archived\",\n \"main-repo\",\n \"pr-open\",\n \"pr-not-merged\",\n \"pr-status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"pr\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"open\",\n \"merged\",\n \"closed\",\n \"none\",\n \"unknown\"\n ]\n },\n \"number\": {\n \"type\": \"number\"\n },\n \"reason\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"safetyCheck\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/safetyCheck\"\n },\n \"worktreeCleanup\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/worktreeCleanup\"\n },\n \"trashDeletion\": {\n \"$ref\": \"#/jsonSchemas/paneArchiveResult/oneOf/0/properties/trashDeletion\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"prWatchResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"generation\": {\n \"type\": \"number\"\n },\n \"watch\": {\n \"type\": \"object\"\n },\n \"watches\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"removed\": {\n \"type\": \"boolean\"\n },\n \"epoch\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"inboxListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"session\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"session\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"sessionId\",\n \"kind\",\n \"at\",\n \"paneId\",\n \"paneName\",\n \"panelId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"report\",\n \"report.missing\",\n \"blocked\"\n ]\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"panelTitle\": {\n \"type\": \"string\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"$ref\": \"#/jsonSchemas/agentReport\"\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"answers\": {\n \"type\": \"array\",\n \"description\": \"The answers the Session's workers got, kept 7 days. Absent from an older Pane.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"answer\",\n \"by\",\n \"worker\",\n \"answeredAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"answer\": {\n \"type\": \"string\"\n },\n \"by\": {\n \"enum\": [\n \"you\",\n \"autopilot\",\n \"orchestrator\",\n \"pane\"\n ],\n \"description\": \"Who answered.\"\n },\n \"because\": {\n \"type\": \"string\"\n },\n \"worker\": {\n \"type\": \"string\",\n \"description\": \"The worker that asked.\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"answeredAt\": {\n \"type\": \"string\"\n },\n \"delivered\": {\n \"enum\": [\n \"call\",\n \"tab\"\n ],\n \"description\": \"How it reached the worker; absent while its tab is busy.\"\n },\n \"undelivered\": {\n \"type\": \"string\",\n \"description\": \"Why Pane can't type it into the worker's tab (its tab closed, or Pane can't see what runs there): pass it on yourself.\"\n },\n \"decision\": {\n \"type\": \"string\",\n \"description\": \"The D-n recorded for it; absent: run `runpane inbox ack <id> --decision <D-n>`.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"inboxAckResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"acknowledged\",\n \"unknown\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"acknowledged\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"unknown\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"dispatchWaitResult\": {\n \"type\": \"object\",\n \"description\": \"What `--wait` prints, as one line: how the dispatched work ended. Exit codes: 0 report, 3 missing, 4 blocked, 5 exited or gone, 124 timeout. Exit 6 is indeterminate after journal replacement or history loss; inspect the panel and inbox before retrying.\",\n \"required\": [\n \"ok\",\n \"outcome\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"true only for outcome report.\"\n },\n \"outcome\": {\n \"enum\": [\n \"report\",\n \"missing\",\n \"blocked\",\n \"exited\",\n \"gone\",\n \"timeout\",\n \"indeterminate\"\n ]\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"generation\": {\n \"type\": \"number\",\n \"description\": \"The journal entry that ended the wait (on timeout, the position reached).\"\n },\n \"at\": {\n \"type\": \"string\"\n },\n \"report\": {\n \"type\": \"object\",\n \"required\": [\n \"state\"\n ],\n \"properties\": {\n \"state\": {\n \"enum\": [\n \"ready\",\n \"blocked\",\n \"failed\",\n \"done\"\n ]\n },\n \"pr\": {\n \"type\": \"number\"\n },\n \"head\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"summaryTruncated\": {\n \"const\": true\n },\n \"summaryPath\": {\n \"type\": \"string\"\n },\n \"question\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screenLines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"lastOutput\": {\n \"type\": \"string\"\n },\n \"lastOutputSource\": {\n \"enum\": [\n \"transcript\",\n \"screen\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": \"number\"\n },\n \"nextCommand\": {\n \"type\": \"string\",\n \"description\": \"Recovery command when the outcome is indeterminate.\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopInput\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"cron\",\n \"enabled\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"loop\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"cron\",\n \"enabled\",\n \"createdAtMs\",\n \"nextRunAtMs\",\n \"scheduleText\",\n \"runs\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"projectId\": {\n \"type\": \"number\"\n },\n \"orchestrationSessionId\": {\n \"type\": \"string\"\n },\n \"cron\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"claude\",\n \"codex\",\n \"cursor\"\n ]\n },\n \"toolCommand\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n },\n \"worktreeTemplate\": {\n \"type\": \"string\"\n },\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"createdAtMs\": {\n \"type\": \"number\"\n },\n \"nextRunAtMs\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"runs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"loopId\",\n \"slotAtMs\",\n \"trigger\",\n \"status\",\n \"error\",\n \"sessionId\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"loopId\": {\n \"type\": \"string\"\n },\n \"slotAtMs\": {\n \"type\": \"number\"\n },\n \"trigger\": {\n \"enum\": [\n \"schedule\",\n \"manual\"\n ]\n },\n \"status\": {\n \"enum\": [\n \"started\",\n \"failed\",\n \"skipped\"\n ]\n },\n \"error\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"sessionId\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loops\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loops\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"loopResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"loop\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"loop\": {\n \"$ref\": \"#/jsonSchemas/loop\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDryRunResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"dryRun\",\n \"input\",\n \"scheduleText\",\n \"nextRunAtMs\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"dryRun\": {\n \"const\": true\n },\n \"input\": {\n \"$ref\": \"#/jsonSchemas/loopInput\"\n },\n \"scheduleText\": {\n \"type\": \"string\"\n },\n \"nextRunAtMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"loopDeleteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"deleted\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"deleted\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyChoice\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The answer notify returns when the person picks this choice.\"\n },\n \"detail\": {\n \"type\": \"string\",\n \"description\": \"One more line about this choice.\"\n },\n \"recommended\": {\n \"type\": \"boolean\",\n \"description\": \"Shade this choice as the one you recommend.\"\n },\n \"was\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"--update only: the label this choice had before it was reworded. A choice whose label stays but whose detail changed may repeat its label here.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifySubQuestion\": {\n \"description\": \"One question of an options approval that asks several at once; the person picks one choice.\",\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"choices\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"notifyAction\": {\n \"type\": \"object\",\n \"required\": [\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"url\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"notifyLink\": {\n \"description\": \"A related file, page or approval shown as a chip on the card: a label and exactly one of path, url or approval.\",\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"minProperties\": 2,\n \"maxProperties\": 2,\n \"properties\": {\n \"label\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 80,\n \"description\": \"The chip's text.\"\n },\n \"path\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A file on the asking machine; a relative path resolves against the caller's working directory.\"\n },\n \"url\": {\n \"type\": \"string\",\n \"pattern\": \"^https?://\\\\S+$\",\n \"description\": \"An http or https page.\"\n },\n \"approval\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Another approval, by its id or Q-n; it opens in place, with Back.\"\n }\n },\n \"additionalProperties\": false\n },\n \"notifyRequest\": {\n \"description\": \"What `runpane notify` and `runpane secrets request` send on the runpane:notify daemon channel: the question fields, with relays or aboutSession when they apply; resume alone; took with tookAs (and status); or update with asked, whatChanged and the changed fields. Only `secrets request` sends ui secret.\",\n \"type\": \"object\",\n \"required\": [\n \"caller\"\n ],\n \"properties\": {\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"secret\",\n \"info\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"questions\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n },\n \"description\": \"ui options, instead of choices: several questions, one pick each.\"\n },\n \"detail\": {\n \"type\": \"string\"\n },\n \"detailLabel\": {\n \"type\": \"string\",\n \"maxLength\": 40\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"action\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n },\n \"secret\": {\n \"type\": \"object\",\n \"description\": \"ui secret only: the Pane secret the answer is saved as.\",\n \"required\": [\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"project\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n },\n \"resume\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"took\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) whose reply the reader took; sent with tookAs and nothing but status.\"\n },\n \"tookAs\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ],\n \"description\": \"answer records the reply as the answer; question shows the reader is getting more context.\"\n },\n \"status\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"tookAs question: what the reader is doing, shown on the approval.\"\n },\n \"update\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval (id or Q-n) to save a new version of; the question fields sent replace those fields. No ui.\"\n },\n \"asked\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: the person's question, as they typed it.\"\n },\n \"whatChanged\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"update: what changed, one line per change.\"\n },\n \"relays\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A waiting item (an id from `runpane inbox`) to improve instead of posting a second card. ui and title are still required.\"\n },\n \"aboutSession\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Autopilot's own item: the Session it is about, an id or exact name.\"\n },\n \"returnAfterMs\": {\n \"type\": \"number\",\n \"description\": \"Return waiting after this long; set for MCP clients whose tool calls time out.\"\n },\n \"caller\": {\n \"type\": \"object\",\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"notifyResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"answer\"\n ],\n \"properties\": {\n \"answer\": {\n \"type\": \"string\",\n \"description\": \"options: the chosen label (with questions, the picks joined with \\\" · \\\"). ask: the typed answer.\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n },\n \"answers\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"options with questions: one pick per question, in order.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"The person typed a reply: it may answer the question or ask one. The approval keeps waiting; say which with `runpane notify --took <replyId> answer|question` (see next).\",\n \"required\": [\n \"reply\",\n \"id\",\n \"replyId\",\n \"next\"\n ],\n \"properties\": {\n \"reply\": {\n \"type\": \"string\",\n \"description\": \"What the person typed.\"\n },\n \"picks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"What they picked with it, one label per question.\"\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The approval, for --took and --update.\"\n },\n \"replyId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"This call's private id for the reply (not the id other screens show). Pass it to --took and --update: only this call was given it, so it lets its agent continue, also from outside a Pane tab.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"--took: how the reply was recorded.\",\n \"required\": [\n \"took\",\n \"id\"\n ],\n \"properties\": {\n \"took\": {\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the person typed with their answer.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"delivered\"\n ],\n \"properties\": {\n \"delivered\": {\n \"enum\": [\n \"screen\",\n \"inbox\"\n ],\n \"description\": \"screen: shown on the strip. inbox: listed in the Notify inbox because a question was on screen.\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person saved the value as project/name. The value is never returned.\",\n \"required\": [\n \"status\",\n \"project\",\n \"name\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"saved\"\n },\n \"project\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"description\": \"secret: the person declined; nothing was saved.\",\n \"required\": [\n \"status\"\n ],\n \"properties\": {\n \"status\": {\n \"const\": \"declined\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"waiting\",\n \"id\",\n \"next\"\n ],\n \"properties\": {\n \"waiting\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"unavailable\",\n \"next\"\n ],\n \"properties\": {\n \"unavailable\": {\n \"type\": \"string\",\n \"description\": \"Why nobody can answer through Pane.\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"workspaceMachine\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"dnsName\",\n \"os\",\n \"online\",\n \"ips\",\n \"self\",\n \"owner\",\n \"mine\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Tailscale machine name; pass it as `machine`.\"\n },\n \"dnsName\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"online\": {\n \"type\": \"boolean\"\n },\n \"ips\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"self\": {\n \"type\": \"boolean\",\n \"description\": \"This machine.\"\n },\n \"owner\": {\n \"type\": \"string\",\n \"description\": \"Tailscale login of the machine's owner.\"\n },\n \"mine\": {\n \"type\": \"boolean\",\n \"description\": \"Signed in to this machine's own login.\"\n }\n }\n },\n \"workspaceListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"self\",\n \"machines\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"self\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"properties\": {\n \"workspace\": {\n \"description\": \"This machine's workspace state; null when Pane is not running.\",\n \"anyOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"enabled\",\n \"state\"\n ],\n \"properties\": {\n \"enabled\": {\n \"type\": \"boolean\"\n },\n \"state\": {\n \"enum\": [\n \"on\",\n \"off\"\n ]\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"fix\": {\n \"type\": \"string\"\n },\n \"machineName\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n }\n },\n \"machines\": {\n \"type\": \"array\",\n \"items\": {\n \"allOf\": [\n {\n \"$ref\": \"#/jsonSchemas/workspaceMachine\"\n }\n ],\n \"required\": [\n \"joined\"\n ],\n \"properties\": {\n \"joined\": {\n \"type\": \"boolean\",\n \"description\": \"Its Pane answers workspace read, write, and exec.\"\n },\n \"info\": {\n \"type\": \"object\",\n \"required\": [\n \"hostname\",\n \"os\",\n \"isWsl\",\n \"shell\",\n \"homeDir\",\n \"wslDistros\"\n ],\n \"properties\": {\n \"hostname\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"isWsl\": {\n \"type\": \"boolean\"\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"homeDir\": {\n \"type\": \"string\"\n },\n \"wslDistros\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"error\": {\n \"type\": \"string\",\n \"description\": \"Why it does not answer.\"\n }\n }\n }\n }\n }\n },\n \"workspaceReadResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"encoding\",\n \"content\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine read from; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"encoding\": {\n \"enum\": [\n \"utf8\",\n \"base64\"\n ]\n },\n \"content\": {\n \"type\": \"string\",\n \"description\": \"File text, or base64 bytes; a directory lists its entries.\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceWriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"path\",\n \"bytes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"machine\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The machine written to; null is this machine.\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"bytes\": {\n \"type\": \"number\"\n }\n }\n },\n \"workspaceExecResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"machine\",\n \"os\",\n \"shell\",\n \"cwd\",\n \"exitCode\",\n \"signal\",\n \"timedOut\",\n \"stdout\",\n \"stderr\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"The command exited 0.\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"os\": {\n \"enum\": [\n \"macOS\",\n \"Windows\",\n \"Linux\"\n ]\n },\n \"shell\": {\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": \"string\"\n },\n \"exitCode\": {\n \"type\": [\n \"number\",\n \"null\"\n ]\n },\n \"signal\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"stillRunning\": {\n \"const\": true,\n \"description\": \"It timed out and could not be stopped.\"\n },\n \"stdout\": {\n \"type\": \"string\"\n },\n \"stderr\": {\n \"type\": \"string\"\n }\n }\n },\n \"autopilotStackEntry\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"ref\",\n \"title\",\n \"tabs\",\n \"addedAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"ref\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"notifyId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"item\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"notifyId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"path\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"file\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"url\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"url\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"label\",\n \"sessionId\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"session\"\n },\n \"label\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"description\": \"The first tab is the entry's own (its approval, document, URL, or Session).\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"addedAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"About something Autopilot wrote or asked itself.\"\n },\n \"doneAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms when it was resolved (answered, expired, or dismissed).\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"One entry on Autopilot's stack. References only: the item, file, URL, or Session it is about.\"\n },\n \"autopilotStatusResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"enabled\",\n \"chatTabsEnabled\",\n \"entitlement\",\n \"run\",\n \"panelId\",\n \"ownerPaneId\",\n \"state\",\n \"sentinel\",\n \"stack\",\n \"walk\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"enabled\": {\n \"type\": \"boolean\",\n \"description\": \"The experimental switch (Settings › Advanced › Experimental › Autopilot).\"\n },\n \"chatTabsEnabled\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot runs as a chat tab, so it needs chat tabs on.\"\n },\n \"entitlement\": {\n \"type\": \"object\",\n \"required\": [\n \"entitled\"\n ],\n \"properties\": {\n \"entitled\": {\n \"type\": \"boolean\"\n },\n \"reason\": {\n \"type\": \"string\"\n },\n \"next\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"run\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"folder\",\n \"docsFolder\",\n \"createdAt\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"folder\": {\n \"type\": \"string\"\n },\n \"docsFolder\": {\n \"type\": \"string\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"panelId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"Autopilot's chat tab, once the run has one.\"\n },\n \"ownerPaneId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"state\": {\n \"enum\": [\n \"stopped\",\n \"running\",\n \"paused\"\n ]\n },\n \"sentinel\": {\n \"type\": \"boolean\",\n \"description\": \"Sentinel Mode: Autopilot keeps Sessions moving while the person is away (turning it on also starts Autopilot).\"\n },\n \"sentinelSince\": {\n \"type\": \"number\",\n \"description\": \"Epoch milliseconds when Sentinel was turned on; absent while it is off.\"\n },\n \"stack\": {\n \"type\": \"object\",\n \"required\": [\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"walk\": {\n \"type\": \"object\",\n \"required\": [\n \"sessions\"\n ],\n \"properties\": {\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"The Session the walk is on.\"\n },\n \"sessions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"waiting\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"waiting\": {\n \"type\": \"number\",\n \"description\": \"Items waiting on the person in this Session.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotWaitingResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Grouped by Session, top first, in the order Autopilot walks them.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"kind\",\n \"agent\",\n \"pane\",\n \"links\",\n \"createdAt\",\n \"ageMs\",\n \"machine\",\n \"machineId\",\n \"onStack\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"enum\": [\n \"approval\",\n \"question\",\n \"permission\"\n ]\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"description\": \"Absent: not in a Session.\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"type\": \"string\"\n },\n \"pane\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"orchestrator\": {\n \"type\": \"boolean\",\n \"description\": \"The asking tab is the Session's orchestrator.\"\n },\n \"byAutopilot\": {\n \"type\": \"boolean\",\n \"description\": \"Autopilot asked it itself.\"\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"label\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"createdAt\": {\n \"type\": \"number\",\n \"description\": \"Epoch ms.\"\n },\n \"ageMs\": {\n \"type\": \"number\"\n },\n \"machine\": {\n \"type\": \"string\"\n },\n \"machineId\": {\n \"type\": \"string\"\n },\n \"onStack\": {\n \"type\": \"boolean\",\n \"description\": \"Already on the stack.\"\n },\n \"quiet\": {\n \"type\": \"boolean\",\n \"description\": \"A worker's question its orchestrator still has first call on: it shows, but doesn't ring yet.\"\n },\n \"stale\": {\n \"type\": \"boolean\",\n \"description\": \"Unanswered for 3 days: it never rings or pushes again.\"\n },\n \"connected\": {\n \"type\": \"object\",\n \"description\": \"What the item is connected to: its Session, or outside one its Pane, Autopilot's chat or its machine. Absent from an older machine.\",\n \"required\": [\n \"kind\",\n \"id\",\n \"name\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"session\",\n \"pane\",\n \"autopilot\",\n \"machine\"\n ]\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n },\n \"offline\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Saved machines it can't reach. Absent from an older Pane.\"\n },\n \"cantSee\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The person's Tailscale machines not saved here, so their items can't show. Absent from an older Pane.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entries\",\n \"shownId\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entries\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n }\n },\n \"shownId\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"description\": \"The entry on screen; null when none is.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotStackPushRequest\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"item\": {\n \"type\": \"string\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Absolute: runpane resolves a relative path against the caller's directory.\"\n },\n \"url\": {\n \"type\": \"string\"\n },\n \"session\": {\n \"type\": \"string\",\n \"description\": \"Session id or exact name.\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"note\": {\n \"type\": \"string\"\n },\n \"tabs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"url\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"next\": {\n \"type\": \"boolean\",\n \"description\": \"Right after the entry on screen, so it comes up next.\"\n }\n },\n \"additionalProperties\": false,\n \"description\": \"Exactly one of item, file, url, or session.\"\n },\n \"autopilotStackPushResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"entry\",\n \"shown\",\n \"position\",\n \"total\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"entry\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/jsonSchemas/autopilotStackEntry\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"shown\": {\n \"type\": \"boolean\",\n \"description\": \"The entry is on screen now (the stack had nothing open).\"\n },\n \"position\": {\n \"type\": \"number\",\n \"description\": \"1-based place on the stack.\"\n },\n \"total\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot answer` sends on the runpane:autopilot:answer daemon channel.\",\n \"required\": [\n \"id\",\n \"answer\",\n \"because\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"answer\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"choice\"\n ],\n \"properties\": {\n \"choice\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"approved\"\n ],\n \"properties\": {\n \"approved\": {\n \"type\": \"boolean\"\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"text\"\n ],\n \"properties\": {\n \"text\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"because\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A D-n, a document or the person's words.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotAnswerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot close` sends on the runpane:autopilot:close daemon channel.\",\n \"required\": [\n \"id\",\n \"reason\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotCloseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteRequest\": {\n \"type\": \"object\",\n \"description\": \"What `runpane autopilot rewrite` sends on the runpane:autopilot:rewrite daemon channel. Link paths are absolute: runpane resolves a relative path against the caller's directory.\",\n \"required\": [\n \"id\",\n \"ui\",\n \"title\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"An id from `runpane autopilot waiting`; it names the item's machine too.\"\n },\n \"ui\": {\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n },\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"context\": {\n \"type\": \"string\"\n },\n \"choices\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n },\n \"suggestions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"links\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"autopilotRewriteResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"id\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"id\": {\n \"type\": \"string\",\n \"description\": \"The new item's id.\"\n },\n \"machine\": {\n \"type\": \"string\",\n \"description\": \"The machine that holds the item.\"\n }\n },\n \"additionalProperties\": false\n },\n \"pausedMark\": {\n \"type\": \"object\",\n \"required\": [\n \"at\",\n \"by\"\n ],\n \"properties\": {\n \"at\": {\n \"type\": \"string\",\n \"description\": \"When the tab was paused (ISO 8601).\"\n },\n \"by\": {\n \"enum\": [\n \"manual\",\n \"auto\"\n ],\n \"description\": \"manual: a person or agent paused it. auto: auto-pause paused it after idleHours without use.\"\n },\n \"idleHours\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"pauseResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"action\",\n \"scope\",\n \"panelIds\",\n \"unchanged\",\n \"failed\",\n \"message\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"False when a tab could not be resumed; the others still were.\"\n },\n \"action\": {\n \"enum\": [\n \"paused\",\n \"resumed\"\n ]\n },\n \"scope\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"panel\",\n \"pane\",\n \"session\"\n ]\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"paneName\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"sessionName\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Tabs this call paused or resumed.\"\n },\n \"chatTabsNotPaused\": {\n \"type\": \"number\",\n \"description\": \"On a pause: chat tabs in scope, which can't pause yet, so their agents keep running.\"\n },\n \"unchanged\": {\n \"type\": \"number\",\n \"description\": \"Terminal tabs in scope that were already paused (for pause) or not paused (for resume).\"\n },\n \"failed\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"error\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, build the local wrapper with `pnpm --filter runpane build` and run it with patched Node 22.23.4+ (22.x), 24.22.1+ (24.x), or 26.12.1+ (26.x), for example `node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, then route by the rule 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: if the work's branch already has a Pane (find it with `runpane panes list --repo <repo> --json` by matching the PR's head branch against `git -C <worktreePath> branch --show-current`), open a new agent tab in it with `runpane panels create --pane <id> --agent <agent> --initial-input \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`; only when the branch has no Pane yet, create one with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` (the new Pane is pinned by default; add `--no-pinned` for a throwaway Pane, add `--base-branch <ref>` to branch from something other than the repository default, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`. For long-lived supervision, use `runpane watch --follow` instead of polling wait or screen.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use `panes create` only when the work's branch has no Pane yet (new work, or an existing PR with no Pane), since each new Pane creates a worktree. Every later agent for that branch opens as a new tab in its Pane with `panels create --pane <id>`, never a new Pane. Tabs share one checkout: only the implementer changes the branch or HEAD, one writer at a time; review, audit and QA tabs never run `git checkout`, `reset` or `stash`.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: Pane's skills live in `<PANE_DIR>/skills/pane-chat/skills/`, and the Pane Chat entry point is `<PANE_DIR>/skills/pane-chat/pane-orchestrator/SKILL.md`; read the skill that matches the request, such as `discussion`, `create-ticket`, `tdd`, `prepare-pr`, or `review`.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; `panes create` pins the new Pane into the UI's favorite/pin set by default, so no follow-up `panes pin` call is needed and `--no-pinned` is the opt-out for throwaway Panes.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"Read and change a repo's Project Settings (build, run, and archive scripts, worktree folder, environment variables) with `runpane repos get` and `runpane repos update`; never edit Pane's database by hand.\",\n \"When a command needs a secret (an API key, a token), run it with `runpane secrets exec --project <project> -- <command>`. If the secret is missing, ask the person with `runpane secrets request`; never ask them to paste a secret into the chat, and never print one.\",\n \"If WSL cannot reach Pane, the user may be running Windows Pane; try `/mnt/c/Users/<you>/.pane/bin/runpane doctor --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude|cursor> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes, wait for each agent's input box, and verify its starting message in one call.\",\n \"Use `runpane panels screen` for compact current state, including Claude and Codex composer state. Use `panels wait` for create-time readiness or text checks. Use `runpane watch --follow` to block on workspace transitions (READY, BLOCKED, IDLE, STUCK, EXIT) without polling, or `runpane watch --until-event` to exit on the first one. Use `panels submit` to send and submit a new turn. Use `panels submit-composer --strategy auto` only for a composer that was filled separately.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"Pane terminals draw inline images: sixel, iTerm2 inline images, and the kitty graphics protocol. Tools that need kitty graphics, such as terminal-browser and terminal-doom, run inside a Pane panel; `runpane doctor --json` reports the exact list under `terminal.graphicsProtocols`.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen`. For a harness that consumes streaming output, use `runpane watch --follow` (with `--session <id>` for a Session); its named consumer applies settling and batching. Adding numeric --since leaves it unnamed and bypasses cadence; --as cannot be combined with --since. A harness that wakes only when a process exits uses `runpane watch --as <name> --until-event --json` and runs it again after each event. Single-event waits deliver immediately without follow cadence. Never pipe `--follow` into `grep -m1` or `head`.\",\n \"When several agents share one resource, such as a test account, name a lock in their prompts: each worker runs `runpane lock acquire --name <name> --ttl 30m --wait 1800000 --json` before using it and `runpane lock release --name <name>` after.\",\n \"Loops (`runpane loops create`) run a task on a cron schedule, each run in a new Pane or, with `--session`, as a message to a Session's agent. They are for large recurring jobs, such as a nightly bug sweep, and run at most once an hour. For a short wait, use your agent harness's own sleep and wake instead.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude|cursor>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--build-script <script>\",\n \"--run-script <script>\",\n \"--archive-script <script>\",\n \"--worktree-folder <path>\",\n \"--env <json>\",\n \"--env-remove <name,...>\",\n \"--name <name>\",\n \"--clear <setting,...>\",\n \"--from-json <path|->\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"secrets list\",\n \"summary\": \"List Pane secrets: project, name, time and machine. Never a value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment.\",\n \"arguments\": [\n \"--project <project>\",\n \"--only <name,...>\",\n \"--env <VAR=NAME>\",\n \"--file <name>\",\n \"-- <command>...\"\n ]\n },\n {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card; returns saved or declined, never the value.\",\n \"arguments\": [\n \"--project <project>\",\n \"--name <name>\",\n \"--title <text>\",\n \"--reason <text>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--pinned\",\n \"--no-pinned\",\n \"--no-associate\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--session <id|name>\",\n \"--merged\",\n \"--closed\",\n \"--source <user|agent>\",\n \"--force\",\n \"--remove-worktree\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--name <new-name>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally a panel) on explicit user request.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--panel <panel-id>\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Open reviewer, QA, fix or helper agent tabs inside an existing Pane; they share that Pane's worktree. The default for any later agent on existing work.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude|cursor>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels open\",\n \"summary\": \"Show the user an HTML page, plan, report, dev server URL, or file as a tab in split view beside the agent.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--url <url>\",\n \"--file <path>\",\n \"--title <title>\",\n \"--split\",\n \"--tab\",\n \"--no-focus\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened once it no longer matters; refuses the person's tabs, busy agents and unsent input.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--dry-run\",\n \"--force\",\n \"--source <user|agent>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the key for its current state.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter|tab>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels resume\",\n \"summary\": \"Restart a stopped terminal panel in place (same panel id), resuming its agent's conversation.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--session <id|name>\",\n \"--all-stopped\",\n \"--concurrency <count>\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace events from the durable daemon journal; follow uses conservative cadence and quiet expiry by default.\",\n \"arguments\": [\n \"--follow\",\n \"--until-event\",\n \"--session <id|name>\",\n \"--self-test\",\n \"--kinds <kind,...>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question wherever they are and wait for the answer; prefer it to asking in the terminal.\",\n \"arguments\": [\n \"--ui <options|approve|ask|info>\",\n \"--title <text>\",\n \"--choices <json>\",\n \"--links <json>\",\n \"--resume <id>\",\n \"--json\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update Pane and the active supported CLI with durable status.\",\n \"details\": \"Defaults to both components. Use --pane-only or --cli-only to select one. Preflight checks installation ownership and supervisor capability before downtime.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Requested app and CLI release version.\"\n },\n {\n \"name\": \"--pane-only\",\n \"required\": false,\n \"description\": \"Update only the installed Pane app.\"\n },\n {\n \"name\": \"--cli-only\",\n \"required\": false,\n \"description\": \"Update only the invoked supported CLI installation.\"\n },\n {\n \"name\": \"--status\",\n \"value\": \"<operation-id>\",\n \"required\": false,\n \"description\": \"Read durable status without a running Pane.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Wait until the operation completes or requires attention.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Inspect preflight without staging or stopping Pane.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Requires standalone Node and a current-user OS supervisor. npm, managed CLI, and supported pip environments retain their actual ownership. System-package Linux and installations requiring elevation are refused before downtime.\",\n \"The operation preserves a pre-migration data snapshot; it never blindly rolls an old app or database over migrated state.\",\n \"The initial result can be accepted or staging; it is not a claim that the update or restoration completed.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Accepted and ignored, so one --pane-dir can be passed to every runpane command.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n },\n {\n \"name\": \"--report\",\n \"required\": false,\n \"description\": \"Prepare a redacted, inspectable diagnostic report.\"\n },\n {\n \"name\": \"--body-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read report evidence from a file or stdin.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Report title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm creating exactly one greenfield-inc/Pane issue.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\",\n \"runpane doctor --report --title \\\"runpane watch failed\\\" --body-file ./watch.txt --json\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"daemon repair\": {\n \"name\": \"daemon repair\",\n \"summary\": \"Repair and restart the managed remote-daemon launcher without changing pairing or tunnel configuration.\",\n \"details\": \"Use this only after doctor reports a stale or broken remote-daemon launcher. The command invokes the installed Pane executable in service-repair mode and does not rerun remote setup.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Remote daemon data directory; defaults to ~/.pane_remote.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Existing Pane executable used to perform repair.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip the service restart confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print one machine-readable repair result.\"\n }\n ],\n \"examples\": [\n \"runpane daemon repair --pane-dir ~/.pane_remote --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonRepairResult\"\n ],\n \"notes\": [\n \"Repair does not create pairing credentials, change tunnel configuration, or download Pane.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\",\n \"agentContextUnknownCommandError\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\",\n \"An unknown `--command` exits 2. With `--json` it prints `{ ok: false, code: \\\"unknown_command\\\", message, candidates }` on stdout, where `candidates` lists the closest command names first; without `--json` the same candidates go to stderr.\",\n \"`--pane-dir <path>` is accepted and ignored, so one `--pane-dir` can be passed to every runpane command.\"\n ]\n },\n \"mcp\": {\n \"name\": \"mcp\",\n \"summary\": \"Run the Pane MCP server over stdio so coding agents can call runpane commands as tools.\",\n \"details\": \"Use this to register Pane with an MCP client. Pane registers it with Claude Code, Codex, and Cursor automatically; each tool mirrors one runpane command and returns its --json result.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--toolsets\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated toolsets to serve: core (default), all, read, repos, panes, panels, agents, sessions, loops, workspace, git, docs, links, admin.\"\n },\n {\n \"name\": \"--read-only\",\n \"required\": false,\n \"description\": \"Serve only read-only tools.\"\n }\n ],\n \"examples\": [\n \"claude mcp add --scope user pane -- npx --yes runpane@latest mcp\",\n \"codex mcp add pane -- npx --yes runpane@latest mcp\"\n ],\n \"notes\": [\n \"Only the npm package and the Pane app include the MCP server; the Python package prints how to run it with Node.\",\n \"Commands that need --yes in the CLI take `yes: true` as a tool input.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry with `/mnt/c/Users/<you>/.pane/bin/runpane repos list --json`, or with an older Pane build `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"repos get\": {\n \"name\": \"repos get\",\n \"summary\": \"Show a saved repository's Project Settings and the scripts Pane will run.\",\n \"details\": \"Use this before changing a repo's settings, or to find out what a new Pane will run. Each script reports `value` (saved in Project Settings), `fallback` (from the repo's config file), and `effective` (what Pane runs: the saved value when set, otherwise the fallback). `envVars` lists the project environment variables: plain values in full, keychain items and Pane secrets as references, never their values.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name. Defaults to active.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable settings.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos get --repo Pane --json\"\n ],\n \"jsonSchemas\": [\n \"repoGetRequest\",\n \"repoGetResult\"\n ],\n \"notes\": [\n \"Config-file fallbacks are read from the repository root. At run time Pane reads them from each Pane's worktree, so a branch that changes its config file can run something else.\",\n \"`currentBranch` is detected from the repository; it is not a setting.\"\n ]\n },\n \"repos update\": {\n \"name\": \"repos update\",\n \"summary\": \"Change a saved repository's Project Settings: scripts, worktree folder, name, IDE command, system prompt, and environment variables.\",\n \"details\": \"Saves through the same path as the Project Settings dialog: Pane drops its cached project context, rebuilds the run commands, and open windows reload the repo. Settings you do not pass keep their value. Clear a setting with `--clear` or an empty value; a cleared script falls back to the repo's config file. --env adds or replaces project environment variables by key, and --env-remove removes them. A variable is plain text, an existing macOS keychain item, or a Pane secret reference; to store a new secret value, use `runpane secrets request` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Repository display name. It cannot be cleared.\"\n },\n {\n \"name\": \"--build-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Build (setup) script; runs in each new worktree. Newline-separated commands.\"\n },\n {\n \"name\": \"--run-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Run script; runs on the Play button. Newline-separated commands.\"\n },\n {\n \"name\": \"--archive-script\",\n \"value\": \"<script>\",\n \"required\": false,\n \"description\": \"Archive script; runs before a worktree is removed. Newline-separated commands.\"\n },\n {\n \"name\": \"--worktree-folder\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Folder new worktrees go in: absolute, or relative to the repository.\"\n },\n {\n \"name\": \"--open-ide-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command the Open in IDE button runs in a worktree.\"\n },\n {\n \"name\": \"--system-prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Project system prompt.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Project variables to add or replace by key: a JSON array of {key, source: \\\"plain\\\", value}, {key, source: \\\"keychain\\\", service, account} or {key, source: \\\"secret\\\", project, name}.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/projectEnvVar\"\n }\n }\n },\n {\n \"name\": \"--env-remove\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Project variables to remove, comma-separated.\"\n },\n {\n \"name\": \"--clear\",\n \"value\": \"<setting,...>\",\n \"required\": false,\n \"description\": \"Settings to clear, comma-separated: build-script, run-script, archive-script, worktree-folder, open-ide-command, system-prompt.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"JSON settings object with keys name, buildScript, runScript, archiveScript, worktreeFolder, openIdeCommand, systemPrompt, and envVars ({set: [...], remove: [...]}); null clears. Flags override it.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Return the settings as they would be after the update without saving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos update --repo Pane --build-script \\\"pnpm install\\\" --run-script \\\"pnpm dev\\\" --yes --json\",\n \"runpane repos update --repo Pane --clear archive-script --dry-run --json\",\n \"runpane repos update --repo Pane --from-json settings.json --yes --json\",\n \"runpane repos update --repo Pane --env '[{\\\"key\\\":\\\"STRIPE_KEY\\\",\\\"source\\\":\\\"secret\\\",\\\"project\\\":\\\"stripe\\\",\\\"name\\\":\\\"STRIPE_SECRET_KEY\\\"}]' --env-remove OLD_TOKEN --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoUpdateRequest\",\n \"repoUpdateResult\"\n ],\n \"notes\": [\n \"`changed` lists the settings whose saved value differs from before.\",\n \"The worktree folder applies to Panes created after the update; existing worktrees stay where they are.\",\n \"Plain values are saved and shown in full; do not put a secret in a plain variable. Point the variable at a Pane secret instead.\"\n ]\n },\n \"repos rename\": {\n \"name\": \"repos rename\",\n \"summary\": \"Rename a repository saved in the running Pane app.\",\n \"details\": \"Use this to replace an automatic repository name, such as one with a numeric suffix, with a clear one. It changes only the display name.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository to rename: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"New non-empty repository name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated repository.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos rename --repo 3 --name Lev --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoRenameRequest\",\n \"repoRenameResult\"\n ],\n \"notes\": [\n \"The repository path, Panes, and settings are unchanged.\"\n ]\n },\n \"secrets list\": {\n \"name\": \"secrets list\",\n \"summary\": \"List the Pane secrets on this machine: project, name, and when and where each was set. Never shows a value.\",\n \"details\": \"Use this to find the project and name to pass to `runpane secrets exec`. Each entry has project, name, updatedAt and machine. storage says how this machine keeps values; encrypted false means a plain 0600 file on a machine without an OS keyring.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": false,\n \"description\": \"Only this project's secrets.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets list --json\",\n \"runpane secrets list --project stripe --json\"\n ],\n \"jsonSchemas\": [\n \"secretListRequest\",\n \"secretListResult\"\n ],\n \"notes\": [\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets set\": {\n \"name\": \"secrets set\",\n \"summary\": \"Save a Pane secret, adding or replacing it. Reads the value from stdin.\",\n \"details\": \"The value comes only from stdin, so it never appears in argv or a transcript. One trailing newline (\\\\n or \\\\r\\\\n) is dropped; an empty value is refused. If stdin is a terminal it is refused too: pipe the value in, or have the person use Settings > Secrets or `runpane secrets request`. With --json it prints {ok, replaced, secret} (secretSetResult).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name: letters, digits and underscores, not starting with a digit. PATH, WORKTREE_PATH and PANE_* are reserved.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf %s \\\"$VALUE\\\" | runpane secrets set --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretSetRequest\",\n \"secretSetResult\"\n ],\n \"notes\": [\n \"Never set a value someone pasted into the chat; ask with `runpane secrets request` instead, so the value stays out of the transcript.\",\n \"There is no MCP tool for this command: MCP arguments land in the transcript.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets delete\": {\n \"name\": \"secrets delete\",\n \"summary\": \"Delete a Pane secret from this machine and, through sync, from your other machines.\",\n \"details\": \"Deletes <project>/<name> here. Each other machine deletes it the next time a desktop or the phone syncs with it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets delete --project stripe --name STRIPE_SECRET_KEY --yes --json\"\n ],\n \"jsonSchemas\": [\n \"secretDeleteRequest\",\n \"secretDeleteResult\"\n ],\n \"notes\": [\n \"A secret that does not exist fails with SECRET_NOT_FOUND.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\"\n ]\n },\n \"secrets exec\": {\n \"name\": \"secrets exec\",\n \"summary\": \"Run a command with Pane secrets in its environment. Nothing prints a value.\",\n \"details\": \"Reads the secrets from this machine's Pane when it runs, starts the command with your environment plus the secrets, passes stdin, stdout and stderr through, and exits with the command's exit code. By default it injects every secret in the project under its own name. --only limits that to the names given. --env VAR=NAME injects secret NAME as VAR. --file NAME writes secret NAME to a 0600 file in a private temp folder and sets NAME to the file's path; the file is removed when the command exits. Injected names override your environment.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project.\"\n },\n {\n \"name\": \"--only\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Inject only these secrets, comma-separated.\"\n },\n {\n \"name\": \"--env\",\n \"value\": \"<VAR=NAME>\",\n \"required\": false,\n \"description\": \"Inject secret NAME as VAR. Repeatable.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Write secret NAME to a private temp file and set NAME to its path. Repeatable.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command to run and its arguments.\"\n }\n ],\n \"examples\": [\n \"runpane secrets exec --project stripe -- pnpm test:webhooks\",\n \"runpane secrets exec --project stripe --env STRIPE_API_KEY=STRIPE_SECRET_KEY -- node scripts/sync.js\",\n \"runpane secrets exec --project gcp --file SERVICE_ACCOUNT_JSON -- sh -c 'gcloud auth activate-service-account --key-file \\\"$SERVICE_ACCOUNT_JSON\\\"'\"\n ],\n \"notes\": [\n \"Target names PATH, WORKTREE_PATH and PANE_*, in any case, are refused with SECRET_RESERVED_NAME, and nothing runs.\",\n \"A missing secret fails with SECRET_NOT_FOUND before anything runs; ask for it with `runpane secrets request`.\",\n \"Do not echo a secret from inside the command: its output lands wherever the command's output goes, such as your transcript.\",\n \"There is no MCP tool for this command; run it in a shell.\",\n \"Runs on this machine only: `runpane workspace <machine> secrets ...` is refused. Use `runpane workspace <machine> exec -- runpane secrets ...` for another machine.\",\n \"Secrets are the machine owner's: every secrets command in a teammate's `runpane workspace exec` (PANE_TEAMMATE_LOGIN set) fails with SECRET_OWNER_REQUIRED.\"\n ]\n },\n \"secrets request\": {\n \"name\": \"secrets request\",\n \"summary\": \"Ask the person for a secret on a masked card. Pane saves what they type as a Pane secret and never returns it.\",\n \"details\": \"Shows a Notify card with your title and reason, a masked field, Save securely and Decline. What the person types is saved on this machine as the Pane secret <project>/<name> and synced to their other machines. You get back {status: \\\"saved\\\", project, name} or {status: \\\"declined\\\"}, never the value. Then use it with `runpane secrets exec`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--project\",\n \"value\": \"<project>\",\n \"required\": true,\n \"description\": \"Secret project, such as the service or repo it belongs to: up to 64 letters, digits, \\\".\\\", \\\"_\\\" or \\\"-\\\".\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Secret name, such as STRIPE_SECRET_KEY: letters, digits and underscores, not starting with a digit.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What the secret is, in a few words, such as \\\"Stripe test secret key\\\".\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"What you need it for, in one sentence.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane secrets request --project stripe --name STRIPE_SECRET_KEY --title \\\"Stripe test secret key\\\" --reason \\\"Run the webhook integration tests against Stripe's test mode.\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"A result of {waiting: true, id} means the card is still open and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now. Do not ask for the secret in the chat; follow next.\",\n \"Saving replaces a secret that already exists under that name; the card says so.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\",\n \"`status` is `running` while a tool or agent terminal is live, otherwise the stored lifecycle value (`error` and `initializing` always show as stored). The Pane's dock shell (`dock: true` in `panels list`) does not count, so a Pane whose only live terminal is the dock reads its stored value, usually `stopped`. `agentState` is the most urgent state of the live agent panels: `blocked`, `working`, `ready`, or `none`. `agentStatus` (`active`/`idle`) is kept for older clients.\"\n ]\n },\n \"panes cost\": {\n \"name\": \"panes cost\",\n \"summary\": \"Report estimated token cost per Pane, with per-model breakdown and cache efficiency.\",\n \"details\": \"Use this to attribute estimated workspace token costs to Pane lifetimes and inspect the models and cache efficiency behind each Pane's spend.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Return only the selected Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes cost --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"paneCostRequest\",\n \"paneCostResult\"\n ],\n \"notes\": [\n \"The report range is the last 30 days.\",\n \"Costs are estimates based on API token prices.\",\n \"The unattributed bucket reconciles the Pane rows against totals in the unscoped result.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Create a Pane (a new worktree) only when the work's branch has no Pane yet; for review, audit, fix or QA on existing work use `panels create --pane <id>`.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Use this only when the work's branch has no Pane yet: new work, or an existing PR with no Pane. Each new Pane creates a worktree that checks out, installs and often builds, so creating several at once can freeze the machine. First find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`. For review, follow-up review, fixes, QA or any other agent on work that already has a Pane, use `runpane panels create --pane <id>` to open a new agent tab in that Pane instead. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch the new worktree from this ref (for example origin/main or release/2.4) instead of the repository default.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree and branch name; defaults to --name. Characters outside [a-z0-9-] are removed.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<ref>\",\n \"required\": false,\n \"description\": \"Ref to branch from, such as origin/main or release/foo. --base is an alias.\"\n },\n {\n \"name\": \"--branch\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Exact new branch name, slashes included (for example agents/w5a). Fails if the branch exists; never renamed to be unique. Defaults to the worktree name.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Worktree directory name; defaults to --name.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin. --prompt-file is an alias.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--pinned\",\n \"required\": false,\n \"description\": \"Accepted for backward compatibility and now a no-op; `panes create` already pins by default.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Create the pane unpinned instead of the default pinned (the Pane UI's favorite/pin star). Pinning never implies focus.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the created pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until each created panel's agent is at its input box, then deliver and verify any starting message, before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name w5a --base origin/main --branch agents/w5a --agent <agent> --prompt-file prompt.md --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent claude --initial-input-file brief.md --as-file-pointer --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: before creating one, find the feature's Pane with `runpane panes list --repo <repo> --json` by matching the PR's head branch (`gh pr view <n> --json headRefName`) against `git -C <worktreePath> branch --show-current`, and add a tab with `panels create --pane <id>` when it exists.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"`panes create --base <ref> --branch <name> --prompt-file <file>` replaces `git worktree add` plus `panes adopt`: Pane creates the worktree on exactly that branch name. `--branch` is checked with `git check-ref-format --branch` and fails if the branch already exists; `--worktree-name` still names the directory.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved. `panes create` pins the new Pane by default, so a follow-up `panes pin` call is unnecessary; pass `--no-pinned` for throwaway shells or bulk imports. `--pinned` is still accepted and is now a no-op. Inside a Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` set), new Panes are associated with that Session automatically; `--no-associate` opts out. A failed association is reported on the item and never undoes the Pane.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input. A long or multi-line prompt is never typed into the shell: on bash, zsh or sh the agent launches with `\\\"$(cat '<prompt file>')\\\"` reading a private copy under `<pane-dir>/prompts/<pane-id>/`; on PowerShell, cmd, fish or WSL the prompt is pasted into the agent's composer once it is ready.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"With --wait-ready, `initialInput.delivery` reports `taken`, `queued`, `in-composer` or `unknown` with its evidence (`transcript`, `screen`, or `argv` for a prompt passed at launch); verifiedSubmitted is true for `taken` or `queued`. Routing initial input alone does not imply verified submission, and the create's `ok` is false unless a Claude or Codex starting message is verified.\",\n \"If initialInput.blocked.kind is submission_unverified, do not submit again automatically. Inspect initialInput.staged and attempts, then run nextCommand to resolve the ambiguous composer state.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, use Pane's own `runpane` (see doctor) and select the saved WSL repo by name or id, for example `runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json`.\",\n \"Use --wait-ready when an agent needs the started agent at its input box, and its starting message verified, instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"At most 2 Panes run setup at once (worktree file sync, dependency install, build script); more wait in line. A queued `panes create` returns the Pane id right away with `setupQueue` ({ position, message }) and no `panelId`; the agent starts when setup finishes. Find its panel later with `runpane panels list --pane <id> --json`.\",\n \"A Pane with its own worktree starts at the remote's current tip of a remote-tracking base (the default `origin/main`, or one named with `--base`): Pane fetches that branch first, waiting at most about 10 seconds, and creates started together share the fetch. Each item's `base` reports `{ ref, commit, refresh }`, where `refresh` is `fetched`, `recent` (reused a fetch from the last 30 seconds), `skipped` (a local branch or commit base) or `failed`. A failed refresh never fails the create; the Pane starts from the local ref and `base.notice` says what happened and how to catch up.\",\n \"`panes create --branch <name>` fails with code `ERR_RUNPANE_BRANCH_HAS_PANE` when another Pane already has that branch checked out. The message names the Pane and gives the next step: `runpane panels create --pane <id> --tool-command \\\"<cmd>\\\" --source agent --no-focus`.\"\n ]\n },\n \"panes adopt\": {\n \"name\": \"panes adopt\",\n \"summary\": \"Adopt an existing externally managed git worktree as a Pane.\",\n \"details\": \"Validates that the canonical path is a registered worktree of the selected saved repository, then creates a Pane record without creating, syncing, building, removing, or recreating the directory.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Saved repository selector.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<dir>\",\n \"required\": true,\n \"description\": \"Existing git worktree path.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Agent whose terminal to create; with --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom command to stage or launch instead of a built-in agent, such as a wrapper that runs one.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Persist this agent session id and stage its resume command.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Top-level sidebar folder.\"\n },\n {\n \"name\": \"--launch\",\n \"required\": false,\n \"description\": \"Run the agent (or its resume command) immediately.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sent once the launched agent is ready. Requires --launch.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Alias for --initial-input-file; read the prompt from a file or stdin. Requires --launch.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for the launched agent to be ready and the prompt submitted before returning. Requires --launch.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--no-pinned\",\n \"required\": false,\n \"description\": \"Do not pin the adopted pane.\"\n },\n {\n \"name\": \"--no-associate\",\n \"required\": false,\n \"description\": \"Inside a Session orchestrator, do not associate the adopted pane with that Session (associated automatically by default).\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview only.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent codex --resume <id> --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --tool-command \\\"agent-farm run free-range\\\" --agent claude --launch --yes --json\",\n \"runpane panes adopt --repo active --path /path/to/worktree --name imported --agent claude --launch --prompt-file prompt.md --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"The resume command is staged without Enter unless --launch is passed.\",\n \"Archiving never deletes an adopted worktree unless `runpane panes archive --remove-worktree` is used.\",\n \"One of --agent or --tool-command is required.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"--resume needs a built-in agent command; a wrapper command resumes its own way.\",\n \"The adopted Pane reports `status: running` in `panes list` while its tool terminal is live.\",\n \"A prompt (--prompt, --initial-input, --prompt-file, --initial-input-file) or --wait-ready needs --launch; without it adopt fails instead of dropping the prompt.\",\n \"With --launch the result carries the same readiness, initialInput, and nextCommand fields as panes create. With --resume the prompt goes to the resumed conversation's composer once the agent is ready.\",\n \"To start new work on a named branch, prefer `panes create --base <ref> --branch <name>` over `git worktree add` plus adopt.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree, or archive every merged Pane in a Session.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. The safety check refreshes the configured upstream before comparing it to HEAD and includes exact unpushed commit IDs and subjects. When the branch has no upstream or its upstream is gone (GitHub deletes merged branches), a merged pull request whose head is HEAD counts as pushed, so squash-merged branches archive without --force. Refuses unsafe archive operations unless --force is used. Use --dry-run to inspect the same evidence without mutating Pane state. Use --session <id|name> --merged to close out every finished Pane of a Session at once. Archive also refuses a Pane with an agent mid-turn or waiting for an answer (`agent-busy`, listed in `safetyCheck.busyAgents`), or with a tab holding unsent composer text or unsaved editor edits (`unsent-input`, listed in `safetyCheck.unsentInput`), unless --force is used, since archiving stops the agent or loses the input. Add --closed to the Session sweep to also archive Panes whose PR closed without merging.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane/session id to archive. Required unless --session is used.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes, one of its agents is mid-turn or waiting for an answer (archiving stops it), or a tab holds unsent input (archiving loses it).\"\n },\n {\n \"name\": \"--remove-worktree\",\n \"required\": false,\n \"description\": \"Also check and remove an adopted Pane's worktree. Pane-managed worktrees are always removed; the local branch is always kept.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Archive the Panes associated with this named Session instead of one Pane. Requires --merged.\"\n },\n {\n \"name\": \"--merged\",\n \"required\": false,\n \"description\": \"With --session, archive only Panes whose PR is confirmed merged and whose worktree has no uncommitted, untracked or unpushed work; skip the rest with a reason and next step. An open PR is skipped as pr-open, no PR or a closed unmerged PR as pr-not-merged, and a PR state GitHub cannot confirm as pr-status-unknown.\"\n },\n {\n \"name\": \"--closed\",\n \"required\": false,\n \"description\": \"With --session --merged, also archive Panes whose PR closed without merging, with the same unsaved-work checks. Without --closed they are skipped as pr-not-merged.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Refresh and print archive safety evidence without archiving.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --dry-run --json\",\n \"runpane panes archive --pane <pane-id> --remove-worktree --yes --json\",\n \"runpane panes archive --session <id|name> --merged --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --closed --dry-run --json\",\n \"runpane panes archive --session <id|name> --merged --remove-worktree --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveBulkRequest\",\n \"paneArchiveResult\",\n \"paneArchiveBulkResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"Inspect safetyCheck.unpushedCommitDetails for exact commit IDs and subjects; upstreamRefreshed confirms the tracking ref was refreshed first.\",\n \"A --dry-run result sets dryRun:true and wouldArchive without deleting the Pane or its worktree.\",\n \"A successful archive reports worktreeCleanup `completed` once the worktree is gone from its path and from git. trashDeletion `pending` means a large worktree (for example one with node_modules) is still being deleted from .git/pane-trash in the background; that is still ok:true. `timeout` (also ok:true) means removal is still running; only `failed` sets ok:false.\",\n \"safetyCheck.mergedViaPr names the merged PR whose head is HEAD; it needs an authenticated `gh`. Without it, a squash-merged branch whose remote branch was deleted reports unpushed-commits.\",\n \"An adopted Pane (panes adopt) keeps its worktree unless --remove-worktree is passed. `panes restore` does not recreate a removed adopted worktree; the local branch is kept, so `git worktree add <path> <branch>` brings it back.\",\n \"With --session --merged, each item has outcome archived, would-archive (dry run), skipped (with skipped.code and skipped.message), or failed.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\",\n \"Undo an archive with `runpane panes restore --pane <pane-id> --yes`, which recreates the worktree. `runpane links create --pane <pane-id>` gives the user a link to review the Pane first.\",\n \"When the safety check is skipped, `safetyCheck.reason` says why: `external-worktree` (an adopted Pane whose worktree Pane does not own), `main-repo`, `missing-project-context`, or `git-error`. `worktreeWillRemain: true` means archiving leaves the worktree on disk.\",\n \"An agent mid-turn or waiting for an answer blocks archive with `agent-busy` (`safetyCheck.busyAgents` lists them); wait until it is idle with `runpane panels wait --panel <id> --for idle`, then archive again. The Session sweep skips such a Pane with skipped.code `agent-busy`.\",\n \"A tab holding text typed into an agent's composer but not sent, or an editor with unsaved edits, blocks archive with `unsent-input` (`safetyCheck.unsentInput` lists them), the same input `panels close` refuses to lose; send, save or clear it, then archive again. `--force` discards it.\"\n ]\n },\n \"panes pin\": {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to pin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pin --pane <pane-id> --yes --json\",\n \"runpane panes pin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.\",\n \"Pinning does not focus the Pane.\"\n ]\n },\n \"panes unpin\": {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to unpin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes unpin --pane <pane-id> --yes --json\",\n \"runpane panes unpin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.\",\n \"Unpinning does not focus the Pane.\"\n ]\n },\n \"panes pause\": {\n \"name\": \"panes pause\",\n \"summary\": \"Pause a Pane: stop every terminal tab and keep their conversations until the Pane is resumed.\",\n \"details\": \"Pauses every terminal tab of a Pane, run scripts included, until `panes resume` resumes it. A chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`. A Pane reads as paused (`panes list` `paused`) while all of its terminal tabs are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pause --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Find idle Panes with `panes list --json`: `inactiveSince` is the latest terminal output or input in its tabs, or the last time the person selected it.\",\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panes resume\": {\n \"name\": \"panes resume\",\n \"summary\": \"Resume a paused Pane: restart its paused tabs in their saved conversations.\",\n \"details\": \"Resumes a Pane's paused tabs. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes resume --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"panes rename\": {\n \"name\": \"panes rename\",\n \"summary\": \"Rename a Pane without changing its worktree, branch, panels, or focus.\",\n \"details\": \"Trims and sets the Pane display name through the same persistence and session-updated event path used by the UI. Dry-run returns the projected updated pane without mutating.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to rename.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<new-name>\",\n \"required\": true,\n \"description\": \"New non-empty Pane/session name; surrounding whitespace is trimmed.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and return the projected updated pane without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output including the updated pane.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rename --pane <pane-id> --name issue-393 --yes --json\",\n \"runpane panes rename --pane <pane-id> --name issue-393 --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"paneRenameRequest\",\n \"paneRenameResult\"\n ],\n \"notes\": [\n \"Empty and whitespace-only names are rejected; no additional maximum length is imposed beyond the existing UI and database behavior.\",\n \"The JSON result includes the updated pane summary so callers do not need a second panes list request.\"\n ]\n },\n \"panes focus\": {\n \"name\": \"panes focus\",\n \"summary\": \"Raise the Pane window and select a Pane (and optionally one of its panels).\",\n \"details\": \"Brings the Pane desktop window to the foreground and selects the requested Pane exactly like clicking it in the sidebar; when --panel is given it also selects that panel/tab inside the Pane. This steals window focus, so it must only be run on an explicit user request to open, focus, or show a Pane, never proactively. Focusing does not change the Pane's worktree, branch, panels, or pinned state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to focus. Must be an active (non-archived) Pane.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Optional panel/tab id inside the Pane to select; must belong to the focused Pane.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change focus behavior.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands; required in non-interactive contexts because focusing steals window focus.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes focus --pane <pane-id> --source user --yes --json\",\n \"runpane panes focus --pane <pane-id> --panel <panel-id> --source user --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneFocusRequest\",\n \"paneFocusResult\"\n ],\n \"notes\": [\n \"Run this only when the user explicitly asks to open, focus, show, or switch to a Pane; never focus a Pane proactively because it steals the user's window focus.\",\n \"Archived Panes cannot be focused; the command fails clearly instead of falling back to focusing another Pane.\",\n \"If --panel does not belong to the focused Pane, the command fails clearly rather than selecting a different panel.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\",\n \"`agentType` is the agent Pane treats the panel as; `agentDetection` says how it knows (`declared`, `command`, `process`, or `screen`), and `launchCommand` is the command the panel was launched with.\",\n \"`resumeId` is the agent session ID Pane saved for reopening the panel, and `resumeSource` says which rule resumes with it: `agent-farm` (an `agent-farm run` or `agent-farm agent` command), `configured` (the panel's own resume command), or `agent` (a built-in agent's captured session). Both are omitted when no ID is saved.\",\n \"Every Pane has one plain shell titled `Terminal`, its terminal dock: `dock: true` marks it, and the field is omitted on every other panel. Setup progress notices appear there; it is not a tool or agent tab and does not count toward the Pane's `status`. A `--tool-command` tab without `--title` or `--agent` is titled from its command (whole words up to 40 characters).\",\n \"`openedBy` says who opened the tab through runpane: `source` (`agent` or `user`) and, when known, `panelId` of the tab that opened it. It is omitted for the person's tabs and tabs from before Pane recorded it; `panels close` closes only tabs whose `openedBy.source` is `agent`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters. A chat tab has no terminal: input fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--keys\",\n \"value\": \"<name,...>\",\n \"required\": false,\n \"description\": \"Comma-separated key names to press in order: enter, escape, tab, backspace, space, up, down, left, right, ctrl-c, ctrl-d, or any single character.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\",\n \"runpane panels input --panel <panel-id> --keys down,enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"To answer a menu or prompt, prefer `--keys` (for example `--keys down,enter`) over escape sequences in `--text`.\",\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. On a chat tab it returns the newest transcript lines with source acpTranscript. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"The composer object reports whether a Claude or Codex composer is present and whether it holds undelivered text. Placeholder and suggestion text (drawn dim or grey, such as Claude's suggested next prompt) does not count as undelivered text; `composer.ghostText` carries it, and text output marks its line with `⟨suggestion⟩`.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send and submit text to a terminal panel, including idle agent composers.\",\n \"details\": \"Use this for ordinary interactive submissions. For Claude, Pane waits for the composer to appear and show the staged text, then sends Enter on its own and verifies that the composer cleared; Claude keeps an Enter that arrives together with the text as a newline. For a Codex composer, Pane stages the text, waits for paste handling, then queues with Tab while working or submits with Enter when ready, and verifies delivery when visible evidence is available. Wrapper-launched agents are handled the same way once Pane knows the agent (declared, from the foreground process, or from the screen). A Claude or Codex panel with no composer on screen gets nothing: the result is `ok: false` with `blocked.kind: composer-unknown`. Multi-line or long (over 512 characters) text reaches a Claude or Codex composer as one bracketed paste: CRLF becomes LF, and the submit key goes as its own write once the paste has settled, even while the agent is working. Other terminals receive a normalized CR Enter. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--interrupt\",\n \"required\": false,\n \"description\": \"Interrupt the current turn and wait up to 30 seconds for an empty idle composer before delivering. Escape for Codex/Claude; Ctrl+C for Cursor.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --input-file prompt.md --as-file-pointer --wait --yes\",\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\",\n \"runpane panels submit --panel <panel-id> --input-file brief.md --as-file-pointer --yes --json\",\n \"runpane panels submit --panel <panel-id> --text \\\"Stop that approach and follow this correction\\\" --interrupt --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes sequenceName, verifiedSubmitted, delivery and nextCommand. For Claude and Codex, `delivery.state` is `taken` (the agent started a turn with the text), `queued` (it holds the text until its current turn ends), `in-composer` (the text is still in the composer) or `unknown`, and `delivery.evidence` is `transcript` (the agent's own transcript) or `screen`. verifiedSubmitted is true for `taken` or `queued`; never resend those. If ok is false, inspect blocked and do not assume the turn started.\",\n \"Do not follow `panels submit` with `panels submit-composer`; Claude and Codex composer submission is handled atomically.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\",\n \"On `blocked.kind: composer-unknown`, nothing was typed. Check `panels screen`; if the agent is at a prompt Pane does not recognise, use `panels input`.\",\n \"For Claude, text starting with `!`, `#`, `/` or `@` is sent unchanged and the result carries a `warnings` entry (for example `leading-bang-runs-shell`), because Claude Code gives those characters a meaning of its own.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\",\n \"`--attach <file>` (repeatable, with `--as-file-pointer`) copies each file into a folder beside the prompt file and sends `Read and follow <path> (attached: <copy>, ...)`; the result lists the copies in `attachments`.\",\n \"With --interrupt, Pane sends the stop key only while the agent is active, waits for an empty idle composer, then submits. If readiness times out, replacement text is not sent. A queued result is not read yet: delivery.message explains the delay; never resend queued text. Interrupt success requires delivery.state: taken. Unknown agents and occupied composers are rejected without typing.\",\n \"On a chat tab, submit sends an ACP prompt and returns sequenceName=acp, enter=none, operationId and acp-receipt delivery evidence; a prompt sent while the agent works is queued and sent next. Do not blindly resend after a lost response.\"\n ]\n },\n \"panels resume\": {\n \"name\": \"panels resume\",\n \"summary\": \"Restart stopped terminal panels in place, resuming their agents' conversations: one panel, a Session's panels, or every stopped agent panel.\",\n \"details\": \"Use this when `panels list`, `panels screen`, or `panels wait` report `running: false`, or `watch` reports `panel.stopped`, typically after a Pane restart or once its agent exited back to the shell. The panel keeps its id. Pane re-runs the panel's saved launch command resolved to the agent's resume command (Claude `--resume <session>`, `codex resume <session>`, Cursor `--resume`), the same restart the app runs when it shows the panel. It never takes focus. A panel whose agent exited back to its shell has its saved command run again in that shell. On a running panel it does nothing and returns `action: \\\"already-running\\\"`. `--panel` also resumes a paused tab. `--session <id|name>` or `--all-stopped` resumes every stopped agent panel in that scope (plain shells and paused tabs are left alone), `--concurrency` at a time, skips running panels, and returns one result per panel; one failure does not stop the others.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Terminal panel id. Give exactly one of --panel, --session, or --all-stopped.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of this Session's associated Panes.\"\n },\n {\n \"name\": \"--all-stopped\",\n \"required\": false,\n \"description\": \"Resume the stopped agent panels of every non-archived Pane.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Panels restarted at once for --session or --all-stopped; defaults to 3, at most 10. With --wait-ready the resumed panels then wait together, so the call takes the restarts plus one --ready-timeout-ms.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the resumed agent is ready, with the same startup-screen checks as `panels create --wait-ready`.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels resume --panel <panel-id> --wait-ready --yes --json\",\n \"runpane panels resume --session <id|name> --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelResumeResult\",\n \"panelResumeManyResult\"\n ],\n \"notes\": [\n \"If readiness.blocked is present, the agent is at a startup prompt (trust, update, [y/n]); inspect `panels screen` or follow blocked.suggestedCommand. Ask the user before answering trust or permission prompts.\",\n \"A Claude panel resumes the conversation whose session id Pane launched it with; a Codex panel resumes the session id Pane captured from its output. Without one, the panel relaunches its original command.\",\n \"A bulk resume is ok only when every panel resumed and, with --wait-ready, became ready. Check items[].readiness.blocked and items[].error per panel.\"\n ]\n },\n \"panels close\": {\n \"name\": \"panels close\",\n \"summary\": \"Close a tab an agent opened, exactly like closing it in Pane; refuses the person's tabs, busy agents and unsent input unless --force.\",\n \"details\": \"Closes a tab through the same path as closing it in Pane, so a terminal tab's process stops. It never archives the Pane or touches its worktree. Only a tab an agent opened can close: Pane records the opener when a tab is opened with `--source agent` (panels open, panels create, panes create, agents start). It refuses, without closing, a tab not opened by an agent (the person's, and every tab from before Pane recorded openers), a permanent tab, an agent mid-turn or waiting for an answer, unsent composer text or unsaved editor edits, and a live tab whose input Pane cannot read (agents other than Claude and Codex, and chat tabs). A refusal exits 1 with `refused.code`, `refused.message` and `refused.next`. A tab id that no longer exists returns `alreadyClosed: true`. `--dry-run` reports the same without closing. `--force` (only for a stuck tab, or one the person asked you to close) closes it anyway, as the tab's close button does: a busy agent's turn and unsent input are lost, and the result says `forced: true`. A permanent tab still stays.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tab to close.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Report whether the tab would close, and why not, without closing it.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Close it even if an agent didn't open it, its agent is busy, or it holds unsent input. Only for a stuck tab, or one the person asked you to close.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels close --panel <panel-id> --dry-run --json\",\n \"runpane panels close --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCloseRequest\",\n \"panelCloseResult\"\n ],\n \"notes\": [\n \"ok:false with `refused` means the tab was NOT closed. `refused.code` is opened-by-user, permanent, agent-busy, unsent-input, or input-unknown; follow `refused.next`. Use --force only when the tab is stuck or the person asked you to close it.\",\n \"`alreadyClosed: true` (ok:true, closed:false) means no tab has that id any more.\",\n \"With --dry-run, `wouldClose` says whether the close would go ahead and `refused` says why not. Nothing closes; a refused dry run also exits 1.\",\n \"Open tabs with `--source agent` so they count as yours; `panels list` shows who opened each tab as `openedBy`.\"\n ]\n },\n \"panels pause\": {\n \"name\": \"panels pause\",\n \"summary\": \"Pause a terminal tab: stop its process and keep its agent's conversation until it is resumed.\",\n \"details\": \"Pauses one terminal tab until `panels resume --panel <panel-id>`, or its Pane or Session, resumes it. A chat tab can't pause yet: pausing one fails with `ERR_RUNPANE_CHAT_NOT_PAUSABLE` and changes nothing. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels pause --panel <panel-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command. On a chat tab, a text wait (`--for text`) searches only what the agent writes after the wait starts, plus a reply still streaming then, never the person's prompts.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"If stopped is true, the panel's terminal is not running (for example after a Pane restart); run nextCommand (`panels resume`) instead of waiting again.\",\n \"The default timeout and screen are intentionally small for agent context safety.\",\n \"On a chat tab, status comes from the host ACP session (a turn that ended is ready, a pending approval or question is blocked) and the screen field has source=acpTranscript: transcript text, not a terminal screen.\"\n ]\n },\n \"panels last-message\": {\n \"name\": \"panels last-message\",\n \"summary\": \"Read an agent's last reply from its transcript, without scraping the screen.\",\n \"details\": \"Use this when a worker is READY and you need what it said, such as its PR number or result. It reads the Claude or Codex transcript Pane found for the panel and never falls back to the screen.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Agent panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum characters to return; defaults to 20000. The end of the reply is kept.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels last-message --panel <panel-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelLastMessageResult\"\n ],\n \"notes\": [\n \"Without a transcript (a shell panel, another agent, or no reply yet) the result is `{ ok: false, reason: \\\"transcript-unavailable\\\" }` and the command exits 1. Use `panels screen` then.\",\n \"A worker that ran `runpane report` already handed back its state, PR, head, and summary; read those with `agents status` before reading the whole reply.\"\n ]\n },\n \"panels conversation\": {\n \"name\": \"panels conversation\",\n \"summary\": \"Record which conversation a terminal panel's agent is on, so Switch to chat and restarts open that one. A Claude Code SessionStart hook runs it with --hook.\",\n \"details\": \"Pane calls this for you: the Claude Code SessionStart hook that Agent Farm and Pane's plain claude tabs install runs `runpane panels conversation --hook` on start, /resume, /clear and compact. Run it yourself only to tell Pane a panel is on a conversation it could not learn from the hook.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--conversation\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Conversation (agent session) id the panel is on.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<startup|resume|clear|compact>\",\n \"required\": false,\n \"description\": \"What started the conversation.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id; defaults to PANE_PANEL_ID inside a Pane terminal.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id, with --panel; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels conversation --conversation <id> --source resume --json\"\n ],\n \"jsonSchemas\": [\n \"panelConversationRequest\",\n \"panelConversationResult\"\n ],\n \"notes\": [\n \"`sure` in the result is true when the report came since the panel's agent started; a Switch to chat or restart opens a sure conversation, and otherwise opens the folder's most recent one.\",\n \"`--hook` reads the hook's stdin, prints nothing and always exits 0; it is not offered over MCP.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Open a new agent tab (reviewer, auditor, QA, fix or helper) inside an existing Pane, sharing its worktree. 1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane: use this, not panes create, for any later agent on existing work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. This is the default way to start any later agent on work that already has a Pane, including reviewers, follow-up reviewers, auditors, fix implementers, QA and clean-context helpers. The new tab is a fresh agent that shares the Pane's worktree and branch; it does not create a new worktree. Review, audit and QA tabs never run `git checkout`, `reset` or `stash`, and a fix tab starts only after the previous writer has stopped. Use `panes create` only when the work's branch has no Pane yet. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch. With --tool-command, the agent that command runs.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--chat\",\n \"required\": false,\n \"description\": \"Open --agent claude or codex as a chat tab (ACP, no terminal) instead of a terminal tab. Needs chat tabs on in Settings > Advanced.\"\n },\n {\n \"name\": \"--agent-farm\",\n \"value\": \"<profile>\",\n \"required\": false,\n \"description\": \"Start this Agent Farm profile in a chat tab; Agent Farm prints its ACP launch (agent-farm run --print-acp).\"\n },\n {\n \"name\": \"--arg\",\n \"value\": \"<key=value>\",\n \"required\": false,\n \"description\": \"A launch argument for --agent-farm; repeatable.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--attach\",\n \"required\": false,\n \"value\": \"<file>\",\n \"description\": \"Copy a file beside the --as-file-pointer prompt file and list its copy in the pointer line; repeatable. Needs --as-file-pointer; the result lists the copies in `attachments`.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--expect-report\",\n \"required\": false,\n \"description\": \"Expect a `runpane report` even for --source user work. Work an agent sends expects one by default: Pane emits agent.report.missing (with the agent's last output) if the agent goes ready or exits without one.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Fire-and-forget input: don't wait for a report.\"\n },\n {\n \"name\": \"--wait\",\n \"required\": false,\n \"description\": \"Block until this panel reports (exit 0), misses its report (3), waits on a prompt (4), or exits (5); print one JSON line. The way an orchestrator dispatches: run it as a background tool call.\"\n },\n {\n \"name\": \"--timeout\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"With --wait: give up after this long and exit 124 (outcome timeout).\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command \\\"agent-farm run free-range\\\" --agent claude --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\",\n \"With --tool-command, --agent names the agent the command runs (a wrapper such as `agent-farm run`): Pane launches the command unchanged and treats the panel as that agent. Without --agent, Pane detects Claude Code, Codex and Cursor from the foreground process or the agent's screen, and --wait-ready waits for the detected agent's input box; a command that never shows a supported agent is not ready and nothing is typed into it.\",\n \"`--chat` or `--agent-farm` creates a chat tab: --initial-input is its first prompt, sent once when the conversation starts; --wait-ready waits for the agent to start. `panels submit`, `wait`, `screen`, `output`, `last-message`, `report` and `resume` work on chat tabs; `panels input` and `submit-composer` do not.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels open\": {\n \"name\": \"panels open\",\n \"summary\": \"Open a URL or file as a browser/editor tab in a Pane, in split view beside the agent by default.\",\n \"details\": \"Opens a tab inside a Pane (default: the Pane of the calling panel, from PANE_SESSION_ID). Use it to show the user an HTML page, plan, report, local dev server, or file beside the conversation. --url opens a browser tab (http, https, or file). --file opens a file inside the Pane worktree: files open in an editor tab with read-only previews for supported formats; HTML opens as editable source. Tabs open in split view beside the agent by default; --tab keeps them in the current group. An existing tab showing the same URL or file is reused. Never raises or focuses the Pane window.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open the tab in; defaults to PANE_SESSION_ID (the calling panel's Pane, including a Session orchestrator's).\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"http(s) or file URL to open in a browser tab. WSL file URLs use the same automatic path conversion as --file. Pass exactly one of --url or --file.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"File inside the Pane worktree, relative or absolute. WSL panes accept Linux, Windows drive, and WSL UNC paths without manual conversion. Files open in an editor tab; supported formats have read-only previews, and HTML opens as editable source.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Tab title override.\"\n },\n {\n \"name\": \"--split\",\n \"required\": false,\n \"description\": \"Open in split view beside the primary tab group (default).\"\n },\n {\n \"name\": \"--tab\",\n \"required\": false,\n \"description\": \"Open in the current tab group instead of split view.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Open the tab without selecting it.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Select the tab (default). Never raises or focuses the Pane window.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels open --file plan.html --source agent --yes --json\",\n \"runpane panels open --url http://localhost:3000 --source agent --yes --json\",\n \"runpane panels open --pane <pane-id> --file src/app.ts --tab --source agent --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelOpenRequest\",\n \"panelOpenResult\"\n ],\n \"notes\": [\n \"Prefer this over opening the system browser when showing the user a page, plan, report, or dev server.\",\n \"Write plans and reports as self-contained HTML files. Use --url with a file URL to render a page in a live browser, or --file to open its editable source; reopening the same target reuses its tab.\",\n \"When the user views Pane from another computer, a file page loads only files in its own folder and the folders inside it. Keep the page's CSS, scripts and images there, put them inline, or serve the folder over HTTP and open its localhost URL. Give file URLs as the host sees them, not as the other computer would.\",\n \"Opening a tab does not steal window focus; use `panes focus` only on explicit user request.\",\n \"With `--source agent`, Pane records this tab as opened by an agent (and by the calling tab from PANE_PANEL_ID), so `panels close` can close it later.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer. Auto uses Tab to queue while Codex is working and Enter when ready; Pane verifies that the text left the composer. A chat tab has no composer to submit: it fails with CHAT_TAB_NO_TERMINAL; use panels submit.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter|tab>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName, verifiedSubmitted and, for Claude and Codex, delivery (`taken`, `queued`, `in-composer` or `unknown`, with `transcript` or `screen` evidence). If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\",\n \"With `--strategy auto`, if the staged text is still visible in the composer after the first attempt, Pane retries once and checks again: Tab for working Codex, Enter otherwise. It never retries an empty composer.\"\n ]\n },\n \"workspace state\": {\n \"name\": \"workspace state\",\n \"summary\": \"Read one snapshot of every Pane and CLI panel.\",\n \"details\": \"Use this instead of spawning one command per pane or panel.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optionally limit the snapshot to one saved repository.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane workspace state --json\"\n ],\n \"notes\": [\n \"The response carries the daemon epoch and current journal generation.\"\n ]\n },\n \"watch\": {\n \"name\": \"watch\",\n \"summary\": \"Wait for workspace transitions using the daemon journal.\",\n \"details\": \"`runpane watch --follow` is the monitor for an agent: with no other flags it prints WATCH OK once, then one line per event (reports, ready, blocked, idle, exits, Session membership, and PR conflicts, checks, and merges), batches non-urgent lines, and ends itself with WATCH EXPIRED after 3 hours with nothing happening. Set individual duration flags to override these defaults. `--until-event` is for harnesses that wake an agent when a process exits (Claude Code background Bash, Codex): it prints the first event worth waking for immediately, without follow settling or batching, and exits 0, and its named cursor means nothing is missed between runs. Every default is listed in the contract under defaults.watch; each override flag replaces one value.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--follow\",\n \"required\": false,\n \"description\": \"Keep watching, with the defaults in defaults.watch.follow: WATCH OK once, then one line per event; READY after 3m of quiet, BLOCKED after 30s, other lines batched at most every 10m (REPORT, BLOCKED, PR conflicts, and failed checks arrive at once); IDLE reminders at 10m, 30m, 1h, 3h, then daily; WATCH EXPIRED and exit 0 after 3h with nothing but IDLE.\"\n },\n {\n \"name\": \"--until-event\",\n \"required\": false,\n \"description\": \"Wait for the first event in defaults.watch.untilEvent.kinds (agent.report, agent.blocked, panel.exited, pane.gone), print that one line, and exit 0. Inside a Pane terminal the cursor defaults to until-<panel id>, so a re-run resumes where the last stopped. A RESET and the caller's own panel never end the wait. With --timeout-ms, exit 124 with WATCH TIMEOUT when the deadline passes. Cannot be combined with --follow or --self-test.\"\n },\n {\n \"name\": \"--session\",\n \"required\": false,\n \"value\": \"<id|name>\",\n \"description\": \"Follow every Pane associated with a named Session (id or exact name). The daemon re-reads membership on every read, so a Pane associated after arming is included and a detached or archived Pane drops out without a re-arm. The Session's own orchestrator panels are never reported. Cannot be combined with --pane or --all-managed; an unknown Session fails.\"\n },\n {\n \"name\": \"--pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Limit to a Pane; repeatable. Default all managed panes. Cannot be combined with --session.\"\n },\n {\n \"name\": \"--exclude-pane\",\n \"required\": false,\n \"value\": \"<pane-id>\",\n \"description\": \"Exclude a Pane; repeatable.\"\n },\n {\n \"name\": \"--repo\",\n \"required\": false,\n \"value\": \"<selector>\",\n \"description\": \"Limit to a saved repository (active, id, path, or name). Default all repositories.\"\n },\n {\n \"name\": \"--name-contains\",\n \"required\": false,\n \"value\": \"<text>\",\n \"description\": \"Limit by Pane name substring.\"\n },\n {\n \"name\": \"--include-shells\",\n \"required\": false,\n \"description\": \"Include plain shell panels. Default: agent panels only. A report (agent.report) arrives from any panel either way.\"\n },\n {\n \"name\": \"--kinds\",\n \"required\": false,\n \"value\": \"<kind,...>\",\n \"description\": \"Replace the default event kinds: agent.ready, agent.busy, agent.blocked, agent.unknown, agent.idle, pane.created, pane.gone, panel.exited, panel.stopped, panel.paused, panel.unpaused, agent.report, pane.associated, pane.detached, pr.conflicted, pr.checks, pr.merged. Opt-in only, never in a default: agent.report.missing, and Autopilot's item.created, item.answered, session.created, session.archived, orchestrator.turn_ended. Defaults: defaults.watch.follow.kinds under --follow, defaults.watch.untilEvent.kinds under --until-event, every kind for a one-shot read.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"One JSON object per line. Control lines use kinds _ok, _heartbeat, _reconnected, _error, _reset, _dropped, _timeout, and _expired.\"\n },\n {\n \"name\": \"--as\",\n \"value\": \"<consumer-name>\",\n \"required\": false,\n \"description\": \"Name the cursor (at-least-once delivery): 1-128 letters, numbers, dots, underscores, or hyphens. Default under --follow: session-<uuid> with --session, else this panel (PANE_PANEL_ID, shortened to panel-<sha256 prefix> when longer than 64 characters); under --until-event: until-<panel id>; otherwise anonymous.\"\n },\n {\n \"name\": \"--since\",\n \"value\": \"<generation>\",\n \"required\": false,\n \"description\": \"Use an explicit generation cursor instead of a name. Cannot be combined with --as or --follow.\"\n },\n {\n \"name\": \"--from\",\n \"required\": false,\n \"value\": \"<now|earliest>\",\n \"description\": \"Starting point for a new named cursor. Default now (silent baseline); earliest replays the journal.\"\n },\n {\n \"name\": \"--self-test\",\n \"required\": false,\n \"description\": \"Check the daemon watch path without moving any cursor, print WATCH OK, and exit 0.\"\n },\n {\n \"name\": \"--ready-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long a panel must stay quiet before READY. Default 3m. A BUSY, BLOCKED, UNKNOWN, EXIT, or GONE inside the window cancels it. 0 turns it off.\"\n },\n {\n \"name\": \"--blocked-settle\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: how long before BLOCKED is printed; an in-pane answer inside it cancels the line. Default 30s. 0 turns it off.\"\n },\n {\n \"name\": \"--min-interval\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: batch READY, IDLE, NEW, GONE, EXIT, and UNKNOWN lines at most once per interval; REPORT, BLOCKED, pr.conflicted, pr.base_changed, pr.base_deleted, and failed pr.checks arrive at once and carry the held lines. Default 10m. 0 turns batching off.\"\n },\n {\n \"name\": \"--idle-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: the first IDLE reminder after a READY panel stays idle. Default 10m. Later reminders back off to 30m, 1h, 3h, then daily. 0 turns IDLE off.\"\n },\n {\n \"name\": \"--expire-after\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: end the watch with WATCH EXPIRED and exit 0 after this long with no events but IDLE; any other event restarts the clock. Default 3h. 0 never expires.\"\n },\n {\n \"name\": \"--heartbeat\",\n \"value\": \"<duration>\",\n \"required\": false,\n \"description\": \"Override: print a HEARTBEAT line this often as proof of life. Default off; a monitor that wakes an agent should leave it off.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"required\": false,\n \"value\": \"<duration>\",\n \"description\": \"Per-request wait in milliseconds; 0 reads once without waiting. Default 60000, capped at 120000 (the follow loop re-issues requests). With --until-event it is the deadline for the whole wait (exit 124 with WATCH TIMEOUT). Accepts ms/s/m/h units; bare values are milliseconds. 0/off is a nonblocking read only, and is rejected with --follow or --until-event.\"\n },\n {\n \"name\": \"--limit\",\n \"required\": false,\n \"value\": \"<count>\",\n \"description\": \"Maximum entries per response. Default 256.\"\n },\n {\n \"name\": \"--ack-now\",\n \"required\": false,\n \"description\": \"At-most-once named-cursor delivery (advance before you act). Default at-least-once.\"\n },\n {\n \"name\": \"--include-held-input\",\n \"required\": false,\n \"description\": \"Include up to 120 characters of unsubmitted composer text (typed text only; prompt suggestions and placeholders excluded) in JSON entries. Default off: under --follow, JSON entries carry only heldInputPresent: true.\"\n },\n {\n \"name\": \"--no-held-input\",\n \"required\": false,\n \"description\": \"Disable redacted held-input detection under --follow: no STUCK lines, and no heldInputPresent in JSON.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--epoch\",\n \"value\": \"<epoch>\",\n \"description\": \"Journal epoch paired with --since; a mismatch emits _reset instead of silently skipping events.\",\n \"required\": false\n }\n ],\n \"examples\": [\n \"runpane watch --self-test\",\n \"runpane watch --until-event\",\n \"runpane watch --follow\",\n \"runpane watch --session <id|name> --follow\",\n \"runpane watch --session <id|name> --follow --ready-settle 1m --blocked-settle 15s --min-interval 2m\",\n \"runpane watch --follow --json --kinds agent.report,agent.blocked\"\n ],\n \"notes\": [\n \"Every default is in the contract under defaults.watch: follow (what --follow does) and untilEvent (what --until-event waits for). An override flag replaces one value; nothing else needs remembering.\",\n \"Arming: wait for WATCH OK before relying on a --follow monitor. It prints once when the watch is armed. Run the monitor under your harness's background monitor (one line = one notification).\",\n \"Ending: WATCH EXPIRED (exit 0) means nothing happened for --expire-after: say so, and arm a new watch with the next dispatch. A non-zero exit or a WATCH ERROR line means it failed: tell the user and re-arm once; if it dies again, file runpane doctor --report. If the message says the cursor was superseded, keep the replacement monitor and leave the old one stopped. Judge a watch by these lines, never by silence.\",\n \"--until-event prints exactly one line: the event (exit 0), WATCH TIMEOUT (exit 124, only with --timeout-ms), or WATCH ERROR (exit 2).\",\n \"STUCK means real unsubmitted composer text. An agent prompt suggestion (for example a grey Try \\\"...\\\" hint) never counts.\",\n \"Cadence state is held per named consumer. Without numeric --since, follow uses an explicit --as or assigns a Session, panel, or follow-<pid> name. With numeric --since it has no name and bypasses cadence; --as cannot be combined with --since. Held lines survive a reconnect of the same consumer; a changed filter re-delivers them under the new filter.\",\n \"Journal loss is surfaced through reset and dropped metadata.\",\n \"The daemon treats omitted idleAfterMs as disabled so older clients never receive agent.idle unexpectedly.\",\n \"Control lines are not journal entries. Lines text and JSON kind: WATCH OK (_ok, once when armed), HEARTBEAT (_heartbeat, only with --heartbeat), WATCH RECONNECTED (_reconnected, after a retried daemon error), WATCH ERROR (_error; exit 2 unless --follow or --until-event retries it), RESET (_reset, the journal restarted or the cursor fell behind; a fresh baseline follows), DROPPED (_dropped, entries lost to journal truncation), WATCH TIMEOUT (_timeout, --until-event deadline; exit 124), and WATCH EXPIRED (_expired, --expire-after passed; exit 0). Under --until-event, only _timeout and _error print.\",\n \"Under --follow, JSON entries for panels holding unsubmitted composer text carry heldInputPresent: true, the JSON equivalent of the STUCK line. Only typed composer text counts, the same reading as panels screen composer.hasUndeliveredText; an agent's dim or grey prompt suggestion or placeholder does not. --no-held-input turns this off.\",\n \"--session <id|name> scopes the watch to a named Session. The daemon resolves the Session once per request and its associated Panes on every read, so sessions associate and sessions detach take effect without a re-arm; archived and detached Panes drop out, and the Session's own orchestrator panels are excluded. Cadence state is keyed by the Session, not its current Panes, so membership changes keep held lines. A detached Pane's held lines are dropped.\",\n \"Under --session, pane.associated (JOINED <pane-name> pane <pane-id> session <session-id>) and pane.detached (LEFT ...) report membership changes; list them in --kinds next to the agent kinds. Other consumers receive them only when --kinds lists them, so older clients never see an unknown kind.\",\n \"PR watching: the daemon batches up to 25 direct GraphQL fingerprints per host every ~2 minutes. Full details are read on change or a 30-minute backstop. Existing Session branch discovery remains; standalone Panes register with pr watch --url <PR URL> [--head <full-sha>]. Immediate failures, required-check-aware success, head/review/comment/closed/merged events and GitHub outage/recovery events share durable baselines and the journal. A head pin prevents stale success, empty/partial checks never pass, and merge status BLOCKED/UNKNOWN conservatively withholds success. Rate limits honor server waits and reserve 10% for interactive use. Eight other errors or ten comment-only updates stop the watch with pr.stopped. See pr watch/unwatch/list.\",\n \"A daemon that predates --session returns no session field; runpane then fails with WATCH ERROR instead of watching every Pane.\",\n \"Replay: after a reset (RESET/_reset), the baseline restates current state. In JSON, those entries carry replay: true (and changedWhileAway: true after an epoch change). A replayed agent.ready is never READY: re-read runpane sessions overview instead of acting on it. Lines mode prints only CHANGED for them.\",\n \"agent.report (REPORT <pane-name> pane <pane-id> panel <panel-id> <state> [pr#<n>] [<head>][: <question>]) is a worker's `runpane report`. It is in the default kinds of --follow and --until-event, and arrives from any panel: a shell tab or a wrapper launch whose agent is not yet detected included. It bypasses --min-interval, and orchestrators treat it as the completion signal; a READY without a report means look, and maybe nudge.\",\n \"Never pipe --follow into grep -m1, head, or any other exit-on-match reader: it does not exit when its reader does until its next write. To wait for one event, use --until-event.\",\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"--follow has one default cadence: READY 3m, BLOCKED 30s, non-urgent batches 10m, IDLE after 10m with backoff, expiry 3h, heartbeats off. Individual duration flags override it. --present and --profile were removed; use --ready-settle, --blocked-settle, and --min-interval for timing overrides.\",\n \"--until-event delivers matching events immediately without follow settling or batching. A harness that consumes a named follow stream gets its cadence; a process-exit waiter keeps immediate single-event semantics. --follow --since <generation> has no consumer name and bypasses settling and batching. Cadence requires a named follow consumer; --as cannot be combined with numeric --since.\",\n \"Autopilot's kinds reach only watches that list them in --kinds (a Session scope alone does not): item.created and item.answered when an approval or question starts or stops waiting on the person (ITEM|ANSWERED <pane-name> pane <pane-id> [panel <panel-id>] <item-id>: <title>; JSON itemId and reason), session.created and session.archived (SESSION NEW|ARCHIVED <name> pane <pane-id> session <session-id>; paneId is the Session's own Pane), and orchestrator.turn_ended when a Session orchestrator goes from working to idle (TURN ENDED ...). Autopilot waits with `runpane watch --as autopilot --kinds item.created,item.answered,session.created,session.archived,orchestrator.turn_ended --until-event --json`.\"\n ]\n },\n \"panes git-status\": {\n \"name\": \"panes git-status\",\n \"summary\": \"Read the git status of a Pane worktree: uncommitted, unpushed, and behind-main counts.\",\n \"details\": \"Use this to check whether a Pane has work to commit, push, or rebase before archiving it or opening a PR.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes git-status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes commit\": {\n \"name\": \"panes commit\",\n \"summary\": \"Stage all changes in a Pane worktree and commit them.\",\n \"details\": \"Use this to commit a Pane's work the way the app's commit action does. The message needs a title line.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes commit --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes push\": {\n \"name\": \"panes push\",\n \"summary\": \"Push a Pane branch to its remote.\",\n \"details\": \"Use this to publish a Pane's commits, the way the app's push action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes push --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes pull\": {\n \"name\": \"panes pull\",\n \"summary\": \"Pull the remote branch into a Pane worktree.\",\n \"details\": \"Use this to bring remote commits into a Pane, the way the app's pull action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes pull --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes rebase-main\": {\n \"name\": \"panes rebase-main\",\n \"summary\": \"Rebase a Pane branch onto the latest main branch.\",\n \"details\": \"Use this to bring main's changes into a Pane. It rewrites the Pane branch history; conflicts are reported, not resolved.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes rebase-main --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes restore\": {\n \"name\": \"panes restore\",\n \"summary\": \"Restore an archived Pane, recreating its worktree.\",\n \"details\": \"Use this to undo `panes archive`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes restore --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes squash-rebase\": {\n \"name\": \"panes squash-rebase\",\n \"summary\": \"Squash a Pane branch into one commit and rebase it onto main.\",\n \"details\": \"Use this to land a Pane's work on the repository's main branch the way the app's squash-and-rebase action does. It rewrites the Pane branch and updates main.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--message\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"Commit message; the first line is the title.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes squash-rebase --pane <pane-id> --message <message> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash\": {\n \"name\": \"panes stash\",\n \"summary\": \"Stash uncommitted changes in a Pane worktree.\",\n \"details\": \"Use this to set aside work in progress, the way the app's stash action does. `panes stash-pop` brings it back.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes stash-pop\": {\n \"name\": \"panes stash-pop\",\n \"summary\": \"Apply and drop the latest stash in a Pane worktree.\",\n \"details\": \"Use this to bring back work set aside with `panes stash`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stash-pop --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes soft-reset\": {\n \"name\": \"panes soft-reset\",\n \"summary\": \"Undo the last commit in a Pane, keeping its changes staged.\",\n \"details\": \"Use this to reword or regroup the latest commit, the way the app's soft reset action does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes soft-reset --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes fetch\": {\n \"name\": \"panes fetch\",\n \"summary\": \"Fetch the remote for a Pane worktree.\",\n \"details\": \"Use this to refresh remote branches before checking git status or rebasing.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes fetch --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes run-script\": {\n \"name\": \"panes run-script\",\n \"summary\": \"Run the repository's run script in a Pane.\",\n \"details\": \"Use this to start the dev server or app for a Pane, the way the app's Run button does.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes run-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"panes stop-script\": {\n \"name\": \"panes stop-script\",\n \"summary\": \"Stop the run script running in a Pane.\",\n \"details\": \"Use this to stop what `panes run-script` started.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes stop-script --pane <pane-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"panes move\": {\n \"name\": \"panes move\",\n \"summary\": \"Move a Pane into a sidebar folder.\",\n \"details\": \"Use this to organize Panes. Folder ids come from `runpane folders list`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane id from panes list or agents start.\"\n },\n {\n \"name\": \"--folder\",\n \"value\": \"<folder-id>\",\n \"required\": true,\n \"description\": \"Folder id from folders list.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes move --pane <pane-id> --folder <folder-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\",\n \"The result includes `link`, a pane:// link the user can open to review the Pane.\"\n ]\n },\n \"folders list\": {\n \"name\": \"folders list\",\n \"summary\": \"List the sidebar folders of a repository.\",\n \"details\": \"Use this to find folder ids for `panes move`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders list --repo <repo-id> --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"folders create\": {\n \"name\": \"folders create\",\n \"summary\": \"Create a sidebar folder in a repository.\",\n \"details\": \"Use this to add a folder for grouping Panes.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": true,\n \"description\": \"Numeric repository id from repos list.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Folder name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane folders create --repo <repo-id> --name <name> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Runs the same Pane action as the button in the app, through the running Pane daemon.\",\n \"Output is `{ ok, data, error }`; `ok: false` exits non-zero with the error message.\"\n ]\n },\n \"links create\": {\n \"name\": \"links create\",\n \"summary\": \"Build a pane:// link that opens a Pane, panel, repository, or Session in the Pane app.\",\n \"details\": \"Use this to hand the user a clickable link. It only builds the URL; opening it navigates and never changes Pane state.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane to open.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel inside the Pane to select.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<repo-id>\",\n \"required\": false,\n \"description\": \"Repository id from repos list.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<session-id>\",\n \"required\": false,\n \"description\": \"Session id or name from sessions list.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane links create --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"linkCreateResult\"\n ],\n \"notes\": [\n \"Links look like `pane://open?pane=<pane-id>&panel=<panel-id>`. Give exactly one of --pane, --repo, or --session.\"\n ]\n },\n \"links open\": {\n \"name\": \"links open\",\n \"summary\": \"Open a pane:// link in the running Pane app.\",\n \"details\": \"Use this to navigate the app to a Pane, panel, repository, or Session. It raises the Pane window, so run it only when the user asked.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--url\",\n \"value\": \"<pane-url>\",\n \"required\": true,\n \"description\": \"A pane://open link from links create.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane links open --url \\\"pane://open?pane=<pane-id>\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"daemonActionResult\"\n ],\n \"notes\": [\n \"Opening a link never changes Pane state; it selects what the link names.\"\n ]\n },\n \"docs search\": {\n \"name\": \"docs search\",\n \"summary\": \"Search Pane's docs, runpane help, and the Pane Chat skills.\",\n \"details\": \"Use this before guessing how Pane works. Results are short excerpts with a path; read the full text with `runpane docs read`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--query\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"Words to search for.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum results; defaults to 5.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs search --query \\\"archive a pane\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"docsSearchResult\"\n ],\n \"notes\": [\n \"Works offline. Skills are found in the Pane data directory when the Pane app has installed them.\"\n ]\n },\n \"docs read\": {\n \"name\": \"docs read\",\n \"summary\": \"Read one Pane doc, help topic, or skill in full.\",\n \"details\": \"Use this with a path from `runpane docs search`.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--doc\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Path from docs search, such as docs/PANE_MCP.md.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane docs read --doc docs/PANE_MCP.md --json\"\n ],\n \"jsonSchemas\": [\n \"docsReadResult\"\n ],\n \"notes\": [\n \"Paths come from docs search: `docs/<file>.md`, `skills/<name>/SKILL.md`, `help/<topic>`, or `command/<name>`.\"\n ]\n },\n \"agents start\": {\n \"name\": \"agents start\",\n \"summary\": \"Start an agent on a task and wait until it is ready: as a new tab in an existing Pane with --pane <id> (review, audit, fix, QA), or in a new Pane with --repo and --name for new work.\",\n \"details\": \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. For work that already has a Pane, pass --pane <id>: the agent opens as a new tab sharing that Pane's worktree and branch. Only when the work's branch has no Pane yet, pass --repo and --name to create one. It returns the pane and panel ids and a pane:// link to hand the user.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Existing Pane to add the agent to as a new tab. Use this for any work that already has a Pane.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Name for the new Pane. With --repo, only for work whose branch has no Pane yet.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Built-in agent to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<task>\",\n \"required\": true,\n \"description\": \"The task to send once the agent is ready.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"required\": false,\n \"description\": \"Branch to start the worktree from.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents start --repo active --name fix-login --agent claude --prompt \\\"Fix the login redirect\\\" --yes --json\",\n \"runpane agents start --pane <pane-id> --agent codex --prompt \\\"Review the PR from this branch and report findings\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentTaskResult\"\n ],\n \"notes\": [\n \"Creates the Pane in the background (no focus change), pinned, and waits for the agent to be ready before sending the prompt.\",\n \"Check on it with `runpane agents status`; send follow-ups with `runpane agents send`.\"\n ]\n },\n \"agents status\": {\n \"name\": \"agents status\",\n \"summary\": \"Check on an agent: whether it is working, ready, blocked, or idle, plus its current screen.\",\n \"details\": \"Use this to see how an agent started with `agents start` is doing, or whether it needs input.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Screen lines to return; defaults to 40.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents status --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"agentStatusResult\"\n ],\n \"notes\": [\n \"`status` is `working`, `ready`, `blocked`, `idle`, `exited`, or `unknown`. `blocked` means the agent is waiting on a person.\"\n ]\n },\n \"agents send\": {\n \"name\": \"agents send\",\n \"summary\": \"Send a follow-up message to an agent and confirm it was submitted.\",\n \"details\": \"Use this to give an agent its next instruction. To answer a worker that still owes a report, add `--no-expect-report`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id; its agent panel is used.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<message>\",\n \"required\": true,\n \"description\": \"The message to submit.\"\n },\n {\n \"name\": \"--as-file-pointer\",\n \"required\": false,\n \"description\": \"Write the prompt or text to a private prompt file (in the Pane's worktree, hidden from git, when it has one) and send the one line `Read and follow <path>` instead.\"\n },\n {\n \"name\": \"--no-expect-report\",\n \"required\": false,\n \"description\": \"Send a reply (an answer or an addition to current work): it reaches a worker that still owes a report, and Pane keeps waiting for that report.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Confirm this change; required in non-interactive shells.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents send --pane <pane-id> --text \\\"Also add a test\\\" --yes --json\",\n \"runpane agents send --pane <pane-id> --text \\\"$(cat review-notes.md)\\\" --as-file-pointer --yes --json\"\n ],\n \"jsonSchemas\": [\n \"agentSendResult\"\n ],\n \"notes\": [\n \"`delivered` is true when the agent took the message or queued it for after its current turn; `delivery` says which (`taken` or `queued`) and how Pane knows (`transcript` or `screen`). Never resend a delivered message.\",\n \"It types the text, then queues with Tab when Codex is working or submits with Enter when ready. It is for messages, not keys. To answer a menu, use `runpane panels input --panel <panel-id> --keys down,enter --yes`, then check the screen with `runpane agents status`.\",\n \"`--as-file-pointer` writes the text to a prompt file readable only by you, inside the Pane's git worktree at `.pane/prompts/` (hidden by the repository's `.git/info/exclude`, never committed) when it has one, otherwise under `<pane-dir>/prompts/<pane-id>/`, so an agent limited to its working directories reads it without a permission prompt, and sends the one line `Read and follow <path>`, with the path relative to the worktree (where the agent starts) when the file is inside it; the result's `promptFile` is always absolute. Prefer it for long prompts.\"\n ]\n },\n \"report\": {\n \"name\": \"report\",\n \"summary\": \"Hand back a worker's structured report: state, PR, head commit, summary, and the question when blocked.\",\n \"details\": \"Run this as a delegated worker when you finish, fail, or get blocked, as the last step of your task. The orchestrator receives it as an agent.report watch event and treats it as the completion signal. It prints whether anyone was waiting for it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--state\",\n \"value\": \"<ready|blocked|failed|done>\",\n \"required\": true,\n \"description\": \"ready: work is ready for review; done: finished with nothing to review; blocked: needs an answer; failed: could not finish.\"\n },\n {\n \"name\": \"--pr\",\n \"value\": \"<number>\",\n \"required\": false,\n \"description\": \"Pull request number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<sha>\",\n \"required\": false,\n \"description\": \"Head commit the report is about, 7-40 hex characters.\"\n },\n {\n \"name\": \"--summary\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Summary text; up to 16,000 characters are kept.\"\n },\n {\n \"name\": \"--summary-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the summary from a file or stdin; the absolute path is kept as summaryPath.\"\n },\n {\n \"name\": \"--question\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What you need answered; required with --state blocked.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Pane id when not running inside a Pane terminal (PANE_SESSION_ID).\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Panel id when not running inside a Pane terminal (PANE_PANEL_ID).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane report --state ready --pr 747 --head fc5dce9 --summary-file /tmp/report.md --json\",\n \"runpane report --state blocked --question \\\"Should the migration drop the old column?\\\"\"\n ],\n \"jsonSchemas\": [\n \"reportResult\"\n ],\n \"notes\": [\n \"Inside a Pane terminal the report is for the current panel (PANE_SESSION_ID and PANE_PANEL_ID). Explicit --panel (with an optional --pane) wins; with neither, the command fails with a clear error.\",\n \"Each report replaces the panel's previous one and survives a Pane restart.\",\n \"agent.report is in the default kinds of `runpane watch --follow` and `--until-event`, and arrives from any panel, a shell tab included. REPORT lines skip the --min-interval batch.\",\n \"The output says who received it: \\\"Delivered to N waiting watcher(s).\\\" counts watches waiting at that moment (liveWatchers in JSON). With no Session and nobody waiting it adds that the report is stored on the panel (runpane agents status --panel <id>) and that a named-cursor watch (runpane watch --as <name> --kinds agent.report) still receives it when it next waits. Zero is not a failure; the exit code is 0 either way.\",\n \"With --state blocked and --question, the person sees an answerable card at once; it rings once the orchestrator relays it or after 10 minutes. The answer reaches this tab as `answer: Q-n → <answer> (by you)`. `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too.\"\n ]\n },\n \"sessions list\": {\n \"name\": \"sessions list\",\n \"summary\": \"List durable named orchestration Sessions.\",\n \"details\": \"List durable named orchestration Sessions.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions list [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionListResult\"\n ],\n \"notes\": []\n },\n \"sessions create\": {\n \"name\": \"sessions create\",\n \"summary\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"details\": \"Create a durable named orchestration Session and its hidden terminal owner; JSON isPinned sets the pin star (default false).\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions create --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Create-time isPinned requires an updated Pane app. Older apps may ignore the field; use sessions pin after creation and read back the result.\",\n \"runtime appears only on WSL Sessions (\\\"wsl\\\", with wslDistribution, Windows only). A Session on the native host has no runtime field on any OS; \\\"windows\\\" is accepted on input as a legacy name for the native host and is not stored.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"Example: {\\\"name\\\": \\\"Release QA\\\", \\\"goal\\\": \\\"Ship 2.5\\\", \\\"decisions\\\": [\\\"Cut from main\\\"], \\\"blockers\\\": [], \\\"outputs\\\": [{\\\"label\\\": \\\"Release PR\\\", \\\"url\\\": \\\"https://github.com/greenfield-inc/Pane/pull/1\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"pull-request\\\"}]}\"\n ]\n },\n \"sessions get\": {\n \"name\": \"sessions get\",\n \"summary\": \"Read one durable named orchestration Session.\",\n \"details\": \"Read one durable named orchestration Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions get --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions update\": {\n \"name\": \"sessions update\",\n \"summary\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"details\": \"Update a Session overview from structured JSON; isPinned sets the UI pin star.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions update --session <id|name> --from-json <path|-> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Structured input accepts launchCommand (full command including arguments; empty uses the selected agent default) and profile (Session behavior instructions). Creating/opening a Session submits no automatic prompt. Create starts the Session's agent; to give it a first prompt, run `runpane panels wait --panel <panelId> --for ready`, then `runpane panels submit --panel <panelId>`. Changes apply on next terminal launch.\",\n \"JSON isPinned: true/false sets the UI pin star. Creation defaults to false; update preserves the pin when omitted.\",\n \"Payload fields (all optional on update; name required on create): name, goal, context, nextAction, launchCommand, profile (strings); isPinned and, on update, archived (booleans); agent (codex|claude|cursor); decisions and blockers (arrays of strings); evidence and outputs (arrays of links); create also takes runtime (windows|wsl) and wslDistribution; update also takes report, expectedRevision (the revision you read; the update is refused if the Session has moved on) and source (user|agent).\",\n \"A link is {\\\"label\\\": non-empty string, \\\"url\\\": an https://, http://, file:// or grain:// URL, \\\"addedAt\\\": ISO 8601 date such as 2026-10-14T09:30:00Z, \\\"kind\\\"?: evidence|output|ticket|pull-request|other, \\\"provenance\\\"?: string}.\",\n \"archived: true also archives every Pane associated with the Session, as `panes archive --pane` would. A Pane that check refuses stays open (agent-busy, unsent-input, uncommitted-changes, unpushed-commits, uncommitted-and-unpushed, status-unknown), and so does one whose archive fails. The result's `panes` lists `archivedPaneIds`, `keptPanes` (with `code`) and `failedPanes` (with `error`); check a kept Pane with `panes archive --pane <id> --dry-run --json`. Undo is the person's: `panes restore`.\",\n \"Arrays replace the stored value: send the whole list, not only new items.\",\n \"Every problem in the payload is reported at once in one ERR_SESSION_PAYLOAD_INVALID error (paths such as outputs[0].addedAt, \\\"missing\\\" for an absent required field), and nothing is changed.\",\n \"update only: report is {\\\"summary\\\": non-empty string, \\\"status\\\": reported|verified, \\\"evidence\\\": [at least one link], \\\"reportedAt\\\": ISO 8601 date, \\\"provenance\\\": non-empty string}; report null clears it.\",\n \"Example: {\\\"expectedRevision\\\": 4, \\\"nextAction\\\": \\\"Merge after CI\\\", \\\"decisions\\\": [\\\"Cut from main\\\", \\\"Skip beta\\\"], \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\", \\\"kind\\\": \\\"evidence\\\", \\\"provenance\\\": \\\"qa agent\\\"}], \\\"report\\\": {\\\"summary\\\": \\\"QA passed\\\", \\\"status\\\": \\\"verified\\\", \\\"evidence\\\": [{\\\"label\\\": \\\"QA log\\\", \\\"url\\\": \\\"file:///tmp/qa.log\\\", \\\"addedAt\\\": \\\"2026-10-14T09:30:00Z\\\"}], \\\"reportedAt\\\": \\\"2026-10-14T09:31:00Z\\\", \\\"provenance\\\": \\\"qa agent\\\"}}\"\n ]\n },\n \"sessions set-agent\": {\n \"name\": \"sessions set-agent\",\n \"summary\": \"Switch the durable terminal agent for a named Session.\",\n \"details\": \"Switch the durable terminal agent for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions set-agent --session <id|name> --agent <codex|claude|cursor|opencode> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions associate\": {\n \"name\": \"sessions associate\",\n \"summary\": \"Associate a user-visible Pane with a named Session.\",\n \"details\": \"Associate a user-visible Pane with a named Session. `panes create` and `panes adopt` already do this automatically when run inside a Session orchestrator; use this for existing Panes or after `--no-associate`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions associate --session <id|name> --pane <pane-id> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions detach\": {\n \"name\": \"sessions detach\",\n \"summary\": \"Detach a Pane from a named Session.\",\n \"details\": \"Detach a Pane from a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions detach --session <id|name> [--pane <pane-id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": []\n },\n \"sessions overview\": {\n \"name\": \"sessions overview\",\n \"summary\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"details\": \"Read a live status, activity, git, and pull request overview for a named Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions overview --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionOverviewResult\"\n ],\n \"notes\": [\n \"The result includes `locks`: named locks scoped to the Session or held by its Panes (see `runpane lock`).\",\n \"Each `panes[].panels[]` entry has `type`, the tab's panel type (`terminal`, `browser`, `editor`, `explorer`, `diff`, `logs`, `notes`, `dashboard` or `setup-tasks`). A Pane linked whole lists every tab, with `explorer` and `diff` last; a link that names tabs lists exactly those. Only terminals carry an agent `state`; other tabs report `unknown` and do not change the Session's `status`. A missing panel has no `type`, and daemons that predate it omit it.\",\n \"Each `panes[].panels[]` entry has `report`, that panel's latest `runpane report` (state, `pr`, `head`, `question`, `summary`, `summaryTruncated`, `summaryPath`, `reportedAt`). When the summary is a complete JSON object, `report.data` holds it exactly as the worker gave it; Pane never reads or renames its fields. A plain-text, array or scalar summary, invalid JSON, JSON nested more than 64 levels deep, or a truncated summary (`summaryTruncated`) has no `data`. A panel that never reported has no `report` but keeps `state` and `lastActivityAt`. `panes[].report` is still the newest report among the Pane's panels, with its `panelId`; daemons that predate per-panel reports omit `panels[].report`.\"\n ]\n },\n \"lock acquire\": {\n \"name\": \"lock acquire\",\n \"summary\": \"Acquire a named lock on a resource shared between agents, such as one test account, optionally waiting for it.\",\n \"details\": \"Run this before using a resource that only one agent may use at a time, such as a shared test account, and release it when done. The owner is the calling Pane and panel ($PANE_SESSION_ID and $PANE_PANEL_ID, or --pane/--panel). The lock is scoped to the owner's Session when its Pane belongs to one, so two Sessions can each hold `testing-account`; otherwise it is global. It succeeds when the lock is free, expired, or already yours (which renews the TTL). Otherwise it returns ok:false with heldBy and expiresAt at once, or with --wait blocks in the daemon until the lock is released, expires, or its owner's panel exits or Pane is archived.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name: 1-128 letters, digits, \\\".\\\", \\\"_\\\", or \\\"-\\\".\"\n },\n {\n \"name\": \"--ttl\",\n \"value\": \"<duration>\",\n \"required\": true,\n \"description\": \"How long the lock is held before it expires, such as 90s, 30m, or 2h; at most 24h. Acquire again before it runs out to renew.\"\n },\n {\n \"name\": \"--wait\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Wait this long for the lock to come free. The CLI waits in daemon calls of up to 120 seconds each.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"What the lock is for, shown to other agents. Required outside a Pane terminal, where it also names the owner.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock acquire --name testing-account --ttl 30m --wait 1800000 --note \\\"call QA\\\" --json\",\n \"runpane lock acquire --name staging-db --ttl 10m --json\"\n ],\n \"jsonSchemas\": [\n \"lockAcquireRequest\",\n \"lockAcquireResult\"\n ],\n \"notes\": [\n \"Exit status is 0 only when the lock is yours; ok:false (exit 1) means another owner holds it, named by heldBy until expiresAt.\",\n \"Release the lock as soon as you are done with the resource: `runpane lock release --name <name>`. If you interrupt a waiting acquire, the lock can still be granted to you for up to two minutes; release it or let the TTL expire.\",\n \"Pane releases the lock when its TTL runs out, when the owner panel exits, or when the owner Pane is archived. Quitting Pane does not release it; locks survive a restart.\"\n ]\n },\n \"lock release\": {\n \"name\": \"lock release\",\n \"summary\": \"Release a named lock you hold, or force-release another owner's lock.\",\n \"details\": \"Only the owner can release a lock. --force releases another owner's lock, for example a stuck worker's; use --session to name a Session-scoped lock from outside that Session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Lock name.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Release the lock even though another owner holds it.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session whose lock to release; defaults to the caller's Session, or global.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Outside a Pane terminal, the note the lock was acquired with.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": false,\n \"description\": \"Act for this Pane instead of $PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": false,\n \"description\": \"Act for this panel instead of $PANE_PANEL_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock release --name testing-account --json\",\n \"runpane lock release --name testing-account --session \\\"<session-id-or-name>\\\" --force --json\"\n ],\n \"jsonSchemas\": [\n \"lockReleaseResult\"\n ],\n \"notes\": [\n \"released:false with ok:true means no such lock was held. ok:false with reason not-owner (exit 1) means another owner holds it.\"\n ]\n },\n \"lock list\": {\n \"name\": \"lock list\",\n \"summary\": \"List held named locks, optionally only one Session's.\",\n \"details\": \"List every held lock, or with --session the locks scoped to that Session plus those its Panes hold. `runpane sessions overview` includes the same Session locks.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane lock list --json\",\n \"runpane lock list --session \\\"<session-id-or-name>\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"lockListResult\"\n ],\n \"notes\": []\n },\n \"workspace list\": {\n \"name\": \"workspace list\",\n \"summary\": \"List the machines on your tailnet, each with its owner, and whether each answers workspaces.\",\n \"details\": \"Shows this machine's workspace state, then each other Mac, Windows, and Linux machine on your tailnet with its OS, online state, and shell. Your own machines come first; other people's machines carry their owner's login and answer only when their owner lets you in.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace list --json\"\n ],\n \"notes\": [\n \"A machine has joined when Pane runs there with workspaces on; it answers on https://<machine>.<tailnet>.ts.net:8443.\"\n ],\n \"jsonSchemas\": [\n \"workspaceListResult\"\n ]\n },\n \"workspace enable\": {\n \"name\": \"workspace enable\",\n \"summary\": \"Put this machine's Pane on your tailnet for runpane workspace (the default when Tailscale is signed in).\",\n \"details\": \"Serves this Pane's daemon with tailscale serve on port 8443 in HTTP proxy mode. Only your own Tailscale login is accepted unless you set Who can connect to Everyone on tailnet; nothing is opened to the internet.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace enable\"\n ],\n \"notes\": [\n \"Workspaces are on by default when Tailscale is installed and signed in; this undoes runpane workspace disable.\"\n ]\n },\n \"workspace disable\": {\n \"name\": \"workspace disable\",\n \"summary\": \"Take this machine off runpane workspace and remove its tailscale serve handler on port 8443.\",\n \"details\": \"Turns workspaces off for this machine until runpane workspace enable. The remote daemon on port 443 and pairing codes are unaffected.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace disable\"\n ],\n \"notes\": [\n \"Other machines can no longer reach this one; this machine can still reach theirs.\"\n ]\n },\n \"workspace read\": {\n \"name\": \"workspace read\",\n \"summary\": \"Print a file from a machine on your tailnet; any path form works (C:\\\\..., /mnt/c/..., /home/..., ~/...).\",\n \"details\": \"Reads the same way on every OS. On Windows, /mnt/c/... reaches C:\\\\... and WSL paths such as /home/<user>/... are read through \\\\\\\\wsl.localhost\\\\<distro>\\\\. Directories list their entries.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form; ~ is that machine's home.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox read 'C:\\\\Users\\\\me\\\\.pane\\\\plans\\\\topic\\\\index.html'\",\n \"runpane workspace read /mnt/c/Users/me/notes.md\"\n ],\n \"notes\": [\n \"--json returns content with encoding utf8 or base64.\"\n ],\n \"jsonSchemas\": [\n \"workspaceReadResult\"\n ]\n },\n \"workspace write\": {\n \"name\": \"workspace write\",\n \"summary\": \"Write --content or stdin to a file on a machine on your tailnet, creating parent folders.\",\n \"details\": \"Takes the same path forms as workspace read. The content replaces the file.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to route a path that cannot exist here to the one joined machine it fits.\"\n },\n {\n \"name\": \"<path>\",\n \"required\": true,\n \"description\": \"Absolute path in any form.\"\n },\n {\n \"name\": \"--content\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text that replaces the file; an empty string empties it. Without it, the CLI writes stdin. The MCP tool requires it.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"echo done | runpane workspace parsa-devbox write '~/notes/status.md'\"\n ],\n \"notes\": [\n \"Binary stdin is sent as base64 and written byte for byte.\"\n ],\n \"jsonSchemas\": [\n \"workspaceWriteResult\"\n ]\n },\n \"workspace exec\": {\n \"name\": \"workspace exec\",\n \"summary\": \"Run a command in another machine's shell and return stdout, stderr, the exit code, OS, and shell.\",\n \"details\": \"Runs in the shell Pane uses for terminals on that machine (PowerShell or Git Bash on Windows, the login shell elsewhere), with that Pane's runpane first on PATH. The exit code becomes runpane's exit code. Without --, the command starts at the first word that is not an exec option. Without <machine>, a --cwd that cannot exist here picks the machine.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<machine>\",\n \"required\": false,\n \"description\": \"Tailscale machine name, unique prefix, MagicDNS name, or Tailscale IP. Omit it to let a --cwd that cannot exist here pick the one joined machine it fits.\"\n },\n {\n \"name\": \"--cwd\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Working directory on that machine, in any path form; defaults to its home.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"required\": false,\n \"description\": \"Stop the command after this long (default 120000, at most 540000).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"-- <command>\",\n \"required\": true,\n \"description\": \"The command line, run by that machine's shell.\"\n }\n ],\n \"examples\": [\n \"runpane workspace parsa-devbox exec --json -- git -C C:\\\\src\\\\app status --short\"\n ],\n \"notes\": [\n \"This is SSH-level power: a local agent's sandbox and permission prompts see only runpane workspace exec, not what runs on the other machine.\",\n \"Pass the command as one quoted string; several words after -- are joined with spaces, so their own quoting is lost.\"\n ],\n \"jsonSchemas\": [\n \"workspaceExecResult\"\n ]\n },\n \"handoff\": {\n \"name\": \"handoff\",\n \"summary\": \"Hand your task to another agent on this or another machine: check your handoff note, make the branch reachable, send the note, and start the agent on it. A same-agent handoff from a Pane tab carries and resumes your own session (a fork on this machine) and checks it continued; otherwise a fresh agent starts with the note. From a Session's terminal, create the Session on another machine instead.\",\n \"details\": \"Two steps. The agent writes the note, because only it has the context: run `runpane handoff --template > ~/handoff.md` (outside the checkout) and fill in every section (goal, current state, done and verified, in progress, next steps, decisions and constraints, open questions, how to verify, git state), writing \\\"None\\\" where one does not apply. Then `runpane handoff \\\"<destination>\\\" --note-file ~/handoff.md` moves it: it rejects a note with a missing or empty section, refuses an unpushed branch unless --push, finds the destination's saved repository with the same remote, fetches the branch there, writes the note to `~/.pane/handoffs/` on that machine with its origin in the front matter and receiver instructions appended, and starts the agent in a new Pane branched from your branch. When you run in a Pane tab and the receiver is your own agent, it carries your session instead of starting fresh: another machine gets a copy of the session's files in its agent store and resumes the same id; this machine resumes a fork. The receiver starts with no prompt, and the CLI checks it resumed and shows the session's last message, then tells it where the session moved. The receiver verifies the head commit, continues from Next steps, pushes to your branch, and reports back to your panel with `runpane workspace <your machine> panels submit`. Run from a Session's terminal (its folder is outside git), handoff moves the Session instead: it skips git, writes the note to the other machine the same way, and creates a Session there with the same name, goal and agent (numbered \\\"<name> 2\\\" if the name is taken), whose context and next action point at the note; a same-agent Session also gets your conversation's files and resumes it when first opened. The sender Session stays as it is; the new one waits for your first message. A Session cannot be handed off to the machine it is on.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<destination>\",\n \"required\": false,\n \"description\": \"Freeform text naming the agent (claude, codex, cursor), and optionally the model, the effort, and the machine: a Tailscale machine name or unique prefix (list them with `runpane workspace list`), or \\\"this machine\\\". Required unless --agent is given. Examples: \\\"codex gpt-5 high on parsas-macbook-pro\\\", \\\"claude opus on parsa-devbox\\\", \\\"cursor on this machine\\\".\"\n },\n {\n \"name\": \"--template\",\n \"required\": false,\n \"description\": \"Print the note template, with this checkout's (or Session's) origin and git state in its front matter, and exit.\"\n },\n {\n \"name\": \"--note-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"The note to check and send; required unless --template or --dry-run. Without it, --dry-run checks only the destination and git state.\"\n },\n {\n \"name\": \"--machine\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Destination machine; overrides the text.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude|cursor>\",\n \"required\": false,\n \"description\": \"Receiving agent; overrides the text.\"\n },\n {\n \"name\": \"--model\",\n \"value\": \"<model>\",\n \"required\": false,\n \"description\": \"Model for the receiving agent, such as opus, sonnet, gpt-5, or claude-opus-5-5; overrides the text.\"\n },\n {\n \"name\": \"--effort\",\n \"value\": \"<level>\",\n \"required\": false,\n \"description\": \"minimal, low, medium, high, xhigh, or max; supported for Claude and Codex only; overrides the text. Cursor effort is rejected.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Saved repository on the destination. Defaults to the one with a remote for this repository.\"\n },\n {\n \"name\": \"--push\",\n \"required\": false,\n \"description\": \"Commit uncommitted work (except the note) as WIP and push the branch before sending; never forces. Without it, an unpushed branch stops the handoff.\"\n },\n {\n \"name\": \"--note-only\",\n \"required\": false,\n \"description\": \"Start a fresh agent with the note instead of carrying your own session. Without it, a handoff to the same agent carries and resumes your session.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check destination daemon reachability and resolve its saved repository name, path, environment and OS; print the note check and git state without committing, pushing, fetching, writing notes or creating a Pane.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane handoff --template > ~/handoff.md\",\n \"runpane handoff \\\"claude opus on parsas-macbook-pro\\\" --note-file ~/handoff.md --push\",\n \"runpane handoff \\\"codex gpt-5 high on parsa-devbox\\\" --note-file ~/handoff.md --dry-run --json\",\n \"runpane handoff --machine build-server --agent cursor --note-file ~/handoff.md\"\n ],\n \"notes\": [\n \"Another machine is reached through runpane workspace (Tailscale); it needs Pane running there with workspaces enabled and the repository saved. Repository listing and Pane creation call the reached daemon directly; no global destination CLI installation is required.\",\n \"Destination preflight runs before sender commit/push. Inside WSL, an absent or refused local Linux daemon can fall back to its Windows host through reachable Windows Tailscale and enabled workspaces; explicit Pane directory selection keeps its instance. Select a native Windows saved repository; WSL saved repositories are rejected before sending.\",\n \"A successful handoff requires initialInput.verifiedSubmitted and delivery taken or queued without blocked/error evidence. Unverified delivery exits nonzero with the created Pane, panel and a panels screen inspection command; inspect before retrying.\",\n \"From a Session's terminal (PANE_ORCHESTRATION_SESSION_ID set, folder outside git) the receiver is a Session, not a Pane: it needs only runpane:sessions:create and runpane:machine:write on the destination, pushes nothing, and refuses this machine before sending. JSON output then has receiver.route \\\"session\\\", session {id, name, machine} and no git field; a dry run reports the name the Session would get.\",\n \"Write the note for a reader with no context: quote errors and commands exactly, and record approaches that failed so they are not retried. Leave the front matter to the CLI.\",\n \"Model and effort become agent flags: claude --model/--effort, codex -m and -c model_reasoning_effort, cursor-agent --model. Cursor effort is not supported and is rejected before side effects.\",\n \"Run it from the checkout being handed off. On another machine the receiver works in a new Pane; on this machine, from the Pane that owns this checkout, it works in a new tab of that Pane (the result's receiver.route and receiver.reason say which). Either way it pushes back to your branch, so stop changing that branch yourself.\",\n \"JSON continuity: { carried, result: continued | inconclusive | not-continued | fresh-agent, agent, sessionId, resumedId, fork, files, bytes, checks: { resumedById, restartResumes, historyLoaded, originalUntouched?, originalAppended? }, reason, next }; a dry run sets carried, sessionId, fork, files and bytes and no result. inconclusive exits 0 and next says to ask the receiver \\\"What were you last working on? Answer in one or two sentences.\\\"; not-continued exits 1, keeps the receiver and gives the --note-only command for the person to choose. After continued or inconclusive, the sender ends its turn and makes no more changes on the branch. From a Session the result is inconclusive until the new Session is opened. A Cursor session is carried only to another machine (best effort); on this machine the result is not-continued, since a copied Cursor chat still writes to the original. The destination's agent store is assumed at its default location (~/.claude, ~/.codex, ~/.cursor); a copy already there that holds history the sender's lacks, or that an agent there is running, is never overwritten (not-continued). checks.originalAppended is set when the running sender's own turn appended to its transcript during the fork; every byte it held before is unchanged. A destination Pane too old to resume a carried session is refused before anything is sent (ERR_HANDOFF_RESUME_UNSUPPORTED).\"\n ]\n },\n \"sessions unpin\": {\n \"name\": \"sessions unpin\",\n \"summary\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively unpin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions unpin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pin\": {\n \"name\": \"sessions pin\",\n \"summary\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"details\": \"Declaratively pin a named Session using the UI pin state; repeated requests keep the requested state.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pin --session <id|name> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"sessionResult\"\n ],\n \"notes\": [\n \"Uses the same Session update path as the UI. The requested pin state is idempotent.\"\n ]\n },\n \"sessions pause\": {\n \"name\": \"sessions pause\",\n \"summary\": \"Pause a Session: stop the terminal tabs of its chat and every Pane in it, keeping their conversations. Chat tabs keep running.\",\n \"details\": \"Pauses the terminal tabs of a Session's chat and of every Pane in it that is not archived; a chat tab can't pause yet, so its agent keeps running and the result counts it in `chatTabsNotPaused`, until `sessions resume` resumes them. A Session reads as paused (`sessions get` `session.paused`) while its chat and all of its Panes are. Pane stops each terminal tab the way quitting does, without asking, even mid-turn, keeps its agent's conversation, and marks it paused. A paused tab stays stopped through clicks, app restarts and updates; `panels resume --session` and `--all-stopped` skip it. `watch` reports each tab as `panel.paused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions pause --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"Leave paused workers paused unless the person asks to resume them.\"\n ]\n },\n \"sessions resume\": {\n \"name\": \"sessions resume\",\n \"summary\": \"Resume a paused Session: restart its chat and the paused tabs of its Panes.\",\n \"details\": \"Resumes the paused tabs of a Session's chat and Panes. An archived Session is refused. Pane restarts each paused tab in place through the same restart `panels resume` runs, so agents relaunch in their saved conversations and report ready as usual. Running tabs are left alone and counted in `unchanged`. `watch` reports each resumed tab as `panel.unpaused`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": true,\n \"description\": \"Named Session id or exact name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane sessions resume --session <id|name> --json\"\n ],\n \"jsonSchemas\": [\n \"pauseResult\"\n ],\n \"notes\": [\n \"ok is false when a tab could not start; check failed[] for which and why. The others still resumed.\"\n ]\n },\n \"pr watch\": {\n \"name\": \"pr watch\",\n \"summary\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"details\": \"Register durable GitHub PR monitoring for a Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--head\",\n \"value\": \"<full-sha>\",\n \"required\": false,\n \"description\": \"Pin success to this 40-character SHA; otherwise pin the first observed head.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr watch [--pane <id>] --url <PR URL> [--head <full-sha>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr unwatch\": {\n \"name\": \"pr unwatch\",\n \"summary\": \"Remove a Pane’s explicit PR registration.\",\n \"details\": \"Remove a Pane’s explicit PR registration.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<PR URL>\",\n \"required\": true,\n \"description\": \"Full HTTPS GitHub PR URL, including host, repository and number.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr unwatch [--pane <id>] --url <PR URL> [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"pr list\": {\n \"name\": \"pr list\",\n \"summary\": \"List PR registrations and stopped watches.\",\n \"details\": \"List PR registrations and stopped watches.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"Pane id; defaults to PANE_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print JSON (also the default for PR commands).\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane pr list [--pane <id>] [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"prWatchResult\"\n ],\n \"notes\": [\n \"Registration returns generation and epoch. Initially arm watch --until-event --since <generation> --epoch <epoch> --pane <id> --json --kinds pr.checks,pr.head,pr.comment,pr.review,pr.closed,pr.merged,pr.conflicted,pr.base_changed,pr.base_deleted,pr.stopped,github.unavailable,github.recovered. Process every NDJSON event, then persist its gen and epoch for the next --since/--epoch; --limit 1 delivers one event per run. Never mix --as with --since/--epoch. On _reset or _dropped, resynchronize GitHub state, then use the control record generation and epoch; replay/baseline records are not new events. No merge or GitHub write is performed.\",\n \"Watches stop on merge/close, 8 non-rate-limit errors, or 10 comment-only updates. Archived/deleted Panes are removed. Re-register after resolving the reason. Session member PR discovery remains automatic.\"\n ]\n },\n \"inbox\": {\n \"name\": \"inbox\",\n \"summary\": \"List the reports, missing reports, and on-screen questions a Session's orchestrator has not acknowledged, and the answers its workers got.\",\n \"details\": \"An orchestrator runs it when it starts or resumes, to catch reports, missing reports, questions and answers that arrived while it was down. Pane never adds the inbox to the orchestrator's turns.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Session; defaults to PANE_ORCHESTRATION_SESSION_ID.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox --session \\\"$PANE_ORCHESTRATION_SESSION_ID\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"inboxListResult\"\n ],\n \"notes\": [\n \"Items: report (agent.report), report.missing (agent.report.missing), and blocked (agent.blocked, with the prompt's screen lines). Reports coalesce per panel, blockers clear when the panel moves on, and each Session keeps at most 200 items until acknowledged or superseded. A journal replay watermark recovers committed outcomes after restart within journal retention.\",\n \"A newer report replaces the panel's older report or missing report.\",\n \"`answers` lists what the Session's workers were told, kept 7 days. One with no `decision` still needs recording: `runpane inbox ack <id> --decision <D-n>`. An older Pane leaves `answers` out.\"\n ]\n },\n \"inbox ack\": {\n \"name\": \"inbox ack\",\n \"summary\": \"Clear Session inbox items once they are handled, and record an answer's D-n.\",\n \"details\": \"Acknowledge each item once you have acted on it, so the hook stops repeating it. With --decision, also record the D-n of an answer the worker already got.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<item-id>...\",\n \"required\": false,\n \"description\": \"Item ids from `runpane inbox`.\"\n },\n {\n \"name\": \"--decision\",\n \"value\": \"<D-n>\",\n \"required\": false,\n \"description\": \"Record this D-n on each named item, such as D-147. Not with --all.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Only clear items of this Session; required with --all.\"\n },\n {\n \"name\": \"--all\",\n \"required\": false,\n \"description\": \"Clear every item of the Session.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane inbox ack in-1a2b3c4d --json\",\n \"runpane inbox ack in-1a2b3c4d --decision D-147 --json\"\n ],\n \"jsonSchemas\": [\n \"inboxAckResult\"\n ],\n \"notes\": [\n \"Unknown ids are reported under `unknown`; acknowledging twice is harmless.\",\n \"An id already cleared whose answer has no decision still records it.\"\n ]\n },\n \"loops list\": {\n \"name\": \"loops list\",\n \"summary\": \"List loops (scheduled tasks) with their schedule, next run, and recent runs.\",\n \"details\": \"Lists every loop: its id, target (projectId for a repo, or orchestrationSessionId for a Session), cron and scheduleText, enabled, nextRunAtMs (null while paused), and its newest runs. A run's sessionId is its Pane (null for a Session loop's runs); read its progress with `runpane agents status --pane <id>`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops list --json\"\n ],\n \"jsonSchemas\": [\n \"loopListResult\"\n ],\n \"notes\": [\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops create\": {\n \"name\": \"loops create\",\n \"summary\": \"Create a loop: a cron schedule that starts a new Pane in a fresh worktree for each run, or sends a prompt to a Session's agent. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"With --repo, each run is a new Pane in a fresh worktree branched from the repo's default base branch, started like `runpane panes create` with the agent or command and prompt given here. With --session, each run sends the prompt to that Session's agent instead of creating a Pane; a Session loop needs --agent and --prompt and takes no --tool-command. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": true,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--paused\",\n \"required\": false,\n \"description\": \"Create the loop paused.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops create --repo active --name nightly-bug-sweep --cron \\\"0 3 * * *\\\" --agent claude --prompt \\\"Fix one bug from yesterday's issues\\\" --yes --json\",\n \"runpane loops create --repo <repo> --name dependency-check --cron \\\"0 9 * * 1\\\" --tool-command \\\"pnpm test\\\" --yes --json\",\n \"runpane loops create --session new --name morning-triage --cron \\\"0 9 * * 1-5\\\" --agent claude --prompt \\\"Triage the new issues\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": [\n \"Saving rejects a cron whose runs fall less than 60 minutes apart. Check one first with --dry-run.\"\n ]\n },\n \"loops update\": {\n \"name\": \"loops update\",\n \"summary\": \"Change a loop's repo or Session, name, schedule, agent or command, prompt, or worktree template. Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\",\n \"details\": \"Reads the loop, replaces the values you pass, and saves it. Passing --agent clears --tool-command and the other way round; --repo and --session replace each other the same way. Pause and resume with `loops pause` and `loops resume`. Runs start only while Pane is running, in the host's local time zone. A run up to 15 minutes late still starts; a later one is recorded as skipped and never caught up. Runs may overlap. Unattended agent runs skip permission prompts. Pane keeps each loop's last 10 run Panes and archives older ones.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository: active, id, exact path, or saved repository name. Each run is a new Pane in it. Pass this or --session.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name|new>\",\n \"required\": false,\n \"description\": \"Session whose agent gets the prompt each run, by id or name, instead of a new Pane. `new` creates a Session named after the loop. The Session's agent then skips permission prompts for every message it gets, not only loop runs. Pass this or --repo.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Loop name.\"\n },\n {\n \"name\": \"--cron\",\n \"value\": \"<expr>\",\n \"required\": false,\n \"description\": \"Five-field cron in the host's local time zone, such as \\\"0 3 * * *\\\" (every day at 03:00). Runs must be at least 60 minutes apart.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<claude|codex|cursor>\",\n \"required\": false,\n \"description\": \"Agent each run starts; for a Session loop, the Session's agent. Pass this or --tool-command.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Command each run starts instead of a built-in agent, such as `./scripts/nightly-report.sh` or `pnpm test`. Project loops only. On Windows it runs in Pane's terminal shell, Git Bash by default: write bash, or `cmd //c echo hi > loop.txt` rather than `cmd /c`.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text typed into the agent or command after it starts; for a Session loop, the message sent to the Session's agent each run.\"\n },\n {\n \"name\": \"--prompt-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read the prompt from a file or stdin.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<template>\",\n \"required\": false,\n \"description\": \"Worktree and branch name template; defaults to loop-<name>. Project loops only.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Check the cron and print the loop that would be saved, with its schedule and next run, without saving.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops update --loop <loop-id> --cron \\\"0 4 * * *\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopInput\",\n \"loopResult\",\n \"loopDryRunResult\"\n ],\n \"notes\": []\n },\n \"loops run\": {\n \"name\": \"loops run\",\n \"summary\": \"Start a run of a loop now. The schedule stays the same.\",\n \"details\": \"Starts a run now, as a new Pane or a message to the loop's Session, even while the loop is paused or another run is still going. The schedule stays the same, and the run counts toward the 10 kept runs.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops run --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": [\n \"The result is the loop; `runs[0]` is the new run, with `status` started or failed and `sessionId` its Pane (null for a Session loop).\",\n \"Loops are for large recurring jobs and run at most once an hour; for short waits, use your agent harness's own sleep and wake.\"\n ]\n },\n \"loops pause\": {\n \"name\": \"loops pause\",\n \"summary\": \"Pause a loop: it starts no scheduled runs until resumed.\",\n \"details\": \"A paused loop starts no scheduled runs. `loops run` still starts one by hand.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops pause --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops resume\": {\n \"name\": \"loops resume\",\n \"summary\": \"Resume a paused loop from its next scheduled time.\",\n \"details\": \"Runs resume at the next scheduled time. Runs missed while paused are not caught up.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops resume --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopResult\"\n ],\n \"notes\": []\n },\n \"loops delete\": {\n \"name\": \"loops delete\",\n \"summary\": \"Delete a loop. It starts no more runs.\",\n \"details\": \"Deletes the loop and its schedule.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--loop\",\n \"value\": \"<id>\",\n \"required\": true,\n \"description\": \"Loop id, from `runpane loops list`.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": true,\n \"description\": \"Confirm the change.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane loops delete --loop <loop-id> --yes --json\"\n ],\n \"jsonSchemas\": [\n \"loopDeleteResult\"\n ],\n \"notes\": []\n },\n \"notify\": {\n \"name\": \"notify\",\n \"summary\": \"Ask the person a question, or tell them something, wherever they are, and get their answer back.\",\n \"details\": \"Prefer this to asking in the terminal whenever you need the person: to pick between options (ui options), to approve or deny an action (ui approve), to type a short answer (ui ask), or to tell them something that needs no answer (ui info). Pane shows it on screen even while the person works in another app, and the call waits until they answer, however long that takes. info returns at once.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask|info>\",\n \"required\": false,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer. info: an update that returns at once. Required unless resume, took or update.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\",\n \"info\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The question or update in one line, about 80 characters; long titles wrap to 3 lines. Required unless resume, took or update.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The card's subtitle: one or two sentences on why it matters, up to 240 characters.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there unless questions: 1 to 9 choices. The answer is the chosen label. Say what each does in detail, and mark the one you recommend. With update, a reworded choice adds was, its old label.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--detail\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only: a preformatted block, such as the command or SQL to approve, behind a disclosure.\"\n },\n {\n \"name\": \"--detail-label\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"ui approve only, with detail: the disclosure's label, such as \\\"Show the SQL\\\"; up to 40 characters. Defaults to \\\"Show details\\\".\",\n \"schema\": {\n \"type\": \"string\",\n \"maxLength\": 40\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--action\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui info only: one button that opens a URL.\",\n \"schema\": {\n \"$ref\": \"#/jsonSchemas/notifyAction\"\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files, pages or approvals, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https. Another approval is {label, approval}, its id or Q-n.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--relays\",\n \"value\": \"<item>\",\n \"required\": false,\n \"description\": \"A Session orchestrator relaying a worker's question: the waiting item's id from `runpane inbox`. The same card gains these choices, context and links instead of a second card showing. ui and title are still required.\"\n },\n {\n \"name\": \"--about-session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"Autopilot's own item: the Session it is about, shown as its connected Session. Without it, the item links to Autopilot's chat.\"\n },\n {\n \"name\": \"--questions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, instead of choices: 2 to 4 questions asked at once, each with 1 to 9 choices and one pick. The result's answers holds one label per question; answer joins them with \\\" · \\\".\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 2,\n \"maxItems\": 4,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifySubQuestion\"\n }\n }\n },\n {\n \"name\": \"--took\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After a reply result or a `reply:` line: the approval's id or Q-n, with tookAs answer (record the reply as the answer) or question (it asked something; find out, then call update). Pass no question fields.\"\n },\n {\n \"name\": \"--took-as\",\n \"value\": \"<answer|question>\",\n \"required\": false,\n \"description\": \"With took: answer or question. On the command line it can follow the id instead (`--took Q-20 answer`).\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"answer\",\n \"question\"\n ]\n }\n },\n {\n \"name\": \"--status\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With took question: what you are doing about it, shown on the approval. Defaults to \\\"Getting more context…\\\".\"\n },\n {\n \"name\": \"--update\",\n \"value\": \"<id|Q-n>\",\n \"required\": false,\n \"description\": \"After you took a reply as a question and found out: the approval's id or Q-n. Saves a new version with any of title, context, choices, questions and links you pass (the rest stay), shows it as updated, and waits on it like any notify call. Needs asked and whatChanged; takes no ui.\"\n },\n {\n \"name\": \"--asked\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: the person's question, as they typed it.\"\n },\n {\n \"name\": \"--what-changed\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"With update: what changed, one line per change.\"\n },\n {\n \"name\": \"--resume\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"The id from a waiting result: keep waiting on that question. Pass no other fields.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane notify --ui options --title \\\"Which database for the cache?\\\" --choices '[{\\\"label\\\":\\\"Redis\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"SQLite\\\"}]' --json\",\n \"runpane notify --ui approve --title \\\"Drop the legacy_users table?\\\" --detail \\\"DROP TABLE legacy_users;\\\" --detail-label \\\"Show the SQL\\\" --json\",\n \"runpane notify --ui ask --title \\\"Which branch should I base this on?\\\" --suggestions '[\\\"main\\\",\\\"release\\\"]' --json\",\n \"runpane notify --ui info --title \\\"PR #812 is ready for review\\\" --action '{\\\"label\\\":\\\"Open PR\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}' --json\",\n \"runpane notify --ui approve --title \\\"Ship the release notes?\\\" --links '[{\\\"label\\\":\\\"Notes\\\",\\\"path\\\":\\\"docs/RELEASE.md\\\"},{\\\"label\\\":\\\"PR #812\\\",\\\"url\\\":\\\"https://github.com/org/repo/pull/812\\\"}]' --json\",\n \"runpane notify --relays in-1a2b3c4d --ui options --title \\\"Q-39: Run the script?\\\" --choices '[{\\\"label\\\":\\\"Run it\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]' --json\",\n \"runpane notify --ui options --title \\\"Ship 2.5 today: which parts go out?\\\" --questions '[{\\\"title\\\":\\\"Release notes\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Ship as written\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Hold\\\"}]},{\\\"title\\\":\\\"Intel Mac build\\\",\\\"choices\\\":[{\\\"label\\\":\\\"Include it\\\"},{\\\"label\\\":\\\"Skip it\\\"}]}]' --json\",\n \"runpane notify --took Q-20 answer --json\",\n \"runpane notify --took Q-20 question --status \\\"Checking whether it can be undone\\\" --json\",\n \"runpane notify --update Q-20 --asked \\\"Can it be undone?\\\" --what-changed \\\"Added: Settings > Search turns it off again\\\" --context \\\"Search takes 4 s; the index brings it under 100 ms. It can be undone from Settings.\\\" --json\",\n \"runpane notify --resume n-4f2a --json\"\n ],\n \"jsonSchemas\": [\n \"notifyRequest\",\n \"notifyResult\"\n ],\n \"notes\": [\n \"Write the card the way the pane MCP server's instructions say: in plain words, what the work is for, what just happened, and what you need.\",\n \"Results: options returns {answer: <chosen label>, note?}, with questions also answers (one label per question); approve returns {approved: true|false, note?}; ask returns {answer}; info returns {delivered: \\\"screen\\\"|\\\"inbox\\\"}.\",\n \"A result of {reply, picks?, id, replyId, next} means the person typed a reply instead of only picking: it may answer the question or ask one. Read it, then call `runpane notify --took <replyId> answer` to record it as the answer, or `runpane notify --took <replyId> question` and find out, then `runpane notify --update <replyId> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with whatever changed, instead of asking again. With no open call the reply reaches your tab as a `reply:` line; `correction:` lines fix how it was taken. --took returns {took, id, next?}. Use its replyId rather than the Q-n: only this call was given it, so it works from outside a Pane tab too.\",\n \"Only the reply's reader may run took and update: the asker, the orchestrator that relayed it, or Autopilot. Anyone else gets ERR_NOTIFY_NOT_READER.\",\n \"A result of {waiting: true, id} means the question is still on screen and your client would otherwise time out: call `runpane notify --resume <id>` right away to keep waiting. After 15 minutes on this loop Pane gives up and returns unavailable.\",\n \"A result of {unavailable, next} means nobody can answer through Pane right now (Pane is not running, Notify is off, or the wait ended): ask in the terminal instead. It is a result, not an error.\",\n \"Inside a Pane terminal the question names its Pane and panel (PANE_SESSION_ID and PANE_PANEL_ID); elsewhere it names the working directory.\",\n \"An older Pane ignores links and shows the card without them.\",\n \"With relays the call waits on the worker's card as on any notify, and the answer also goes straight to the worker's tab as `answer: Q-n → <answer> (by you)`. Then record it: `runpane inbox ack <item> --decision <D-n>`.\",\n \"Invalid input, such as choices with ui ask, 10 choices, or ui with took or update, fails with a message naming the field, and nothing is shown.\"\n ]\n },\n \"autopilot status\": {\n \"name\": \"autopilot status\",\n \"summary\": \"Show Autopilot (experimental): whether it is on and running, whether Sentinel is on, its run, its stack, and what waits in each Session.\",\n \"details\": \"Autopilot (experimental) is one always-running agent above every Session. It is not a Session: its run lives in <PANE_DIR>/autopilot/. Read this first to know whether it is on, entitled, and running.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot status --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStatusResult\"\n ],\n \"notes\": [\n \"Works while Autopilot is off (enabled: false), so a caller can tell off from broken.\"\n ]\n },\n \"autopilot waiting\": {\n \"name\": \"autopilot waiting\",\n \"summary\": \"List every approval and question waiting on the person on every machine Autopilot can reach, grouped by Session, and the machines it can't reach. This is Autopilot's queue.\",\n \"details\": \"Autopilot's queue, read from each machine's table with no copy: every item waiting on the person on every machine it can reach, filed under its Session (or not in one), this machine's first, with who asked, how long it has waited, whether it is on the stack, and whether it is stale or quiet. `offline` names saved machines it can't reach, and `cantSee` names the person's Tailscale machines not saved here.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot waiting --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotWaitingResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off.\",\n \"Its ids go straight to `runpane autopilot answer`, `close`, `rewrite` and `stack push --item`, whichever machine holds the item.\",\n \"A `quiet` item is a worker's question its orchestrator still has first call on; a `stale` one has waited 3 days and no longer rings.\",\n \"An older Pane leaves `offline` and `cantSee` out.\"\n ]\n },\n \"autopilot stack\": {\n \"name\": \"autopilot stack\",\n \"summary\": \"List the entries on Autopilot's stack in order, and which one is on screen.\",\n \"details\": \"The stack holds entries in the order Autopilot put them; one is shown at a time and the next comes up when the person resolves it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackResult\"\n ],\n \"notes\": [\n \"Fails with AUTOPILOT_OFF while Autopilot is off. A resolved entry carries doneAt.\"\n ]\n },\n \"autopilot stack push\": {\n \"name\": \"autopilot stack push\",\n \"summary\": \"Put a waiting item, a document, a URL, or a Session on Autopilot's stack for the person to look at.\",\n \"details\": \"Give exactly one of --item, --file, --url, or --session. A new entry never replaces the one on screen; --next puts it right after it. runpane resolves relative file paths (the entry's and its tabs') against the caller's directory before sending.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--item\",\n \"value\": \"<id>\",\n \"required\": false,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--file\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"A document to show: HTML loads as a page, anything else as text.\"\n },\n {\n \"name\": \"--url\",\n \"value\": \"<url>\",\n \"required\": false,\n \"description\": \"An http(s) page or running app to show.\"\n },\n {\n \"name\": \"--session\",\n \"value\": \"<id|name>\",\n \"required\": false,\n \"description\": \"A Session whose overview to show.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The entry's title; defaults to the item's, file's, or Session's own.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Autopilot's one line about why it is here.\"\n },\n {\n \"name\": \"--tab\",\n \"value\": \"<path-or-url>\",\n \"required\": false,\n \"description\": \"An extra tab: a file path, or an http(s) URL. Repeatable on the CLI.\"\n },\n {\n \"name\": \"--next\",\n \"required\": false,\n \"description\": \"Put it right after the entry on screen, so it comes up next (jumping back up to a higher Session).\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot stack push --item n-1a2b3c --note \\\"The release orchestrator wants a go\\\" --json\",\n \"runpane autopilot stack push --file docs/plan.html --tab https://localhost:5173 --next --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotStackPushRequest\",\n \"autopilotStackPushResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_PAUSED (the person paused Autopilot; wait for Go on), AUTOPILOT_STACK_INVALID (no such item, file, URL, or Session).\",\n \"The result says where the entry went: position of total, and shown when it is on screen now.\"\n ]\n },\n \"autopilot answer\": {\n \"name\": \"autopilot answer\",\n \"summary\": \"Answer a waiting item as Autopilot, with why; delivered at once and recorded \\\"by Autopilot\\\". Never a permission prompt.\",\n \"details\": \"Give exactly one of --choice, --approve, --deny or --text, and --because. The machine that holds the item delivers the answer at once and records \\\"Answered by Autopilot · because <why>\\\" on every device; the person can Override it. Skill behavior, not a setting: answer only when the way forward is clear and the item relates to your work with the person, or when the person told you to handle approvals.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--choice\",\n \"value\": \"<label>\",\n \"required\": false,\n \"description\": \"The chosen label of an options item.\"\n },\n {\n \"name\": \"--approve\",\n \"required\": false,\n \"description\": \"Approve an approve item.\"\n },\n {\n \"name\": \"--deny\",\n \"required\": false,\n \"description\": \"Deny an approve item.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A typed answer.\"\n },\n {\n \"name\": \"--note\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"A note for the asker, with --choice, --approve or --deny.\"\n },\n {\n \"name\": \"--because\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why, recorded on the item: a D-n, a document or the person's words.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot answer n-1a2b3c --choice \\\"Run it\\\" --because \\\"D-22\\\" --json\",\n \"runpane autopilot answer n-1a2b3c --approve --because \\\"The person said to ship it\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotAnswerRequest\",\n \"autopilotAnswerResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot close\": {\n \"name\": \"autopilot close\",\n \"summary\": \"Close a waiting item as Autopilot, with why; its asker is told and the person can reopen it. Never a permission prompt.\",\n \"details\": \"The machine that holds the item closes it, tells its asker, and records \\\"Closed by Autopilot: <why>\\\" on every device. The person can reopen it.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--reason\",\n \"value\": \"<why>\",\n \"required\": true,\n \"description\": \"Why it no longer needs the person, recorded on the item.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot close n-1a2b3c --reason \\\"The PR it asks about merged yesterday\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotCloseRequest\",\n \"autopilotCloseResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"An item already answered or closed is refused with who did it, and nothing changes: leave it.\"\n ]\n },\n \"autopilot rewrite\": {\n \"name\": \"autopilot rewrite\",\n \"summary\": \"Close an unclear waiting item and re-ask it rewritten; the answer goes to the original asker.\",\n \"details\": \"The old item reads \\\"Closed by Autopilot: rewritten as …\\\" and the new one \\\"Rewritten by Autopilot from Q-n\\\". The rewrite stays on the old item's machine, and its answer goes to the original asker. runpane resolves relative --links paths against the caller's directory; a file must exist on the item's machine.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"<id>\",\n \"required\": true,\n \"description\": \"A waiting item's id from `runpane autopilot waiting`.\"\n },\n {\n \"name\": \"--ui\",\n \"value\": \"<options|approve|ask>\",\n \"required\": true,\n \"description\": \"options: pick one of choices. approve: Approve or Deny. ask: a typed answer.\",\n \"schema\": {\n \"type\": \"string\",\n \"enum\": [\n \"options\",\n \"approve\",\n \"ask\"\n ]\n }\n },\n {\n \"name\": \"--title\",\n \"value\": \"<text>\",\n \"required\": true,\n \"description\": \"The question in one line.\"\n },\n {\n \"name\": \"--context\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"The card's subtitle: one or two sentences on why it matters, up to 240 characters.\"\n },\n {\n \"name\": \"--choices\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui options only, and required there: 1 to 9 choices. The answer is the chosen label. Mark at most the one you recommend.\",\n \"schema\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 9,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyChoice\"\n }\n }\n },\n {\n \"name\": \"--suggestions\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"ui ask only: suggested answers, shown as chips above the text field.\",\n \"schema\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n }\n },\n {\n \"name\": \"--links\",\n \"value\": \"<json>\",\n \"required\": false,\n \"description\": \"Any ui: up to 6 related files or pages, shown as chips on the card. A file is {label, path}, on this machine; a relative path resolves against the working directory. A page is {label, url}, http or https.\",\n \"schema\": {\n \"type\": \"array\",\n \"maxItems\": 6,\n \"items\": {\n \"$ref\": \"#/jsonSchemas/notifyLink\"\n }\n }\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane autopilot rewrite n-1a2b3c --ui options --title \\\"Q-39: Run the import on the copy first?\\\" --choices '[{\\\"label\\\":\\\"Copy first\\\",\\\"recommended\\\":true},{\\\"label\\\":\\\"Skip\\\"}]' --json\"\n ],\n \"jsonSchemas\": [\n \"autopilotRewriteRequest\",\n \"autopilotRewriteResult\"\n ],\n \"notes\": [\n \"Errors: AUTOPILOT_OFF (off), AUTOPILOT_ONLY (not Autopilot's tab), AUTOPILOT_CANNOT_ANSWER (a permission prompt: it stays the person's), ERR_APPROVALS_MACHINE_OFFLINE (the item's machine is offline; nothing is queued), ERR_APPROVALS_OWNER_TOO_OLD (its machine runs an older Pane).\",\n \"The result's id is the new item's. Put that one on the stack.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"This repository is used with [Pane](https://runpane.com). Drive it with the CLI or the `pane` MCP server.\",\n \"\",\n \"1 feature = 1 worktree = 1 branch = 1 Pane; reviews, audits, fixes and QA for it are tabs in that Pane. Open later agents with `runpane panels create --pane <id>` (MCP: `panels_create` or `agents_start` with `pane`), not a new Pane.\",\n \"\",\n \"When you need the person (a choice, an approval, a short answer), call the `notify` MCP tool (or `runpane notify`) instead of asking in the terminal. If it returns `waiting`, call it again with `resume`; if it returns `unavailable`, ask in the terminal. Claude's own card (AskUserQuestion) is only for a quick question in the chat the person is typing in, about their latest message, that steers only this turn; everything else is an approval card, and Codex and other agents always use one. A worker in a Pane Session (`PANE_SESSION_DIR` set, `PANE_ORCHESTRATION_SESSION_ID` unset) doesn't call `notify`: it reports blocked to its orchestrator with `runpane report --state blocked --question \\\"<question>\\\"`. Its orchestrator relays that card with `runpane notify --relays <item> ...`, the answer goes straight to the worker's tab, and the orchestrator records it with `runpane inbox ack <item> --decision <D-n>`. For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`; if that returns `unavailable`, ask the person to save it in Pane under Settings > Secrets.\",\n \"\",\n \"CLI: `npm i -g runpane` (or `npx --yes runpane@latest`), then `runpane doctor --json`. Full command reference: `runpane agent-context --json`.\",\n \"\",\n \"MCP: packaged Pane registers a stdio server named `pane` with Claude Code, Codex, and Cursor. Check the connection with `claude mcp list`, `codex mcp list`, or `agent mcp list`. Cursor may ask you to enable `pane` with `agent mcp enable pane`. If tools are missing, add it in the agent's MCP settings: Claude Code `claude mcp add --scope user pane -- npx --yes runpane@latest mcp`; Codex (`~/.codex/config.toml`) table `[mcp_servers.pane]` with `command = \\\"npx\\\"` and `args = [\\\"--yes\\\", \\\"runpane@latest\\\", \\\"mcp\\\"]`; Cursor (`~/.cursor/mcp.json`) uses `mcpServers.pane` with the same `npx` command and args; any other stdio client uses them too.\"\n ],\n \"notifyGuidance\": {\n \"rules\": [\n \"Which card: Claude's own card (AskUserQuestion) only when you are answering the person's latest message in the chat they are typing in, the answer only steers this turn, and the question needs no context, links or Summary. Everything else is an approval card (`notify`, or `runpane report` for a worker): a worker's question in a Session, a question a Session orchestrator relays, and anything that waits, blocks other work, is recorded as a decision or approves an ask-first action. Unsure? Use an approval card. Codex and other agents without a built-in card always do.\",\n \"In a Session, only its orchestrator asks the person. A worker in a Session (`PANE_SESSION_DIR` is set and `PANE_ORCHESTRATION_SESSION_ID` isn't, or your brief names an orchestrator or a parent status file) doesn't call `notify`: it reports blocked with `runpane report --state blocked --question \\\"<question>\\\"`, and the answer reaches its tab as `answer: Q-n → <answer> (by you)`; `closed: Q-n (by you)` and `correction: Q-n → <answer> (by you)` lines are the person's word too. A Session orchestrator (`PANE_ORCHESTRATION_SESSION_ID` is set) posts each item that waits on the person once, as a background `runpane notify ... --json`, as its pane-orchestration guide says. Any other agent calls `notify`.\",\n \"Ask only what you can't find out yourself.\",\n \"Put related questions in one card.\",\n \"Send `info` rarely, only for what the person must know now.\",\n \"Write the card as its Summary: a title of about 80 to 100 characters on one line; context as a subtitle of one or two sentences saying why it matters; 2 to 4 choices, each saying what it does, with exactly one recommended; links to every file, page or approval it mentions. Pane refuses a title over 160 characters or with a line break, and two recommended choices.\",\n \"A `reply:` line (or a `{reply, replyId}` result) is the person's typed reply: an answer or a question. If it answers, run `runpane notify --took <id|Q-n> answer`. If it asks, run `--took <id|Q-n> question`, find out, then `runpane notify --update <id|Q-n> --asked \\\"<their question>\\\" --what-changed \\\"<one line per change>\\\"` with any changed `--title`, `--context`, `--choices`, `--questions` or `--links` (a reworded choice adds `\\\"was\\\"`, its old label) instead of asking again. A `correction:` line fixes how it was taken. From a `{reply, replyId}` result, pass its replyId to --took and --update.\",\n \"For an API key, token or password, call `secrets_request` (or `runpane secrets request`), never `ask`.\"\n ],\n \"cardRule\": \"The person is juggling many tasks and has lost the thread. In plain words, say what the work is for (the outcome, not the file), what just happened, and what you need. For a decision, give the options and what each one means. Pane already shows which Pane and agent sent it.\",\n \"results\": \"`waiting` means call `notify` again with `resume`. `unavailable` means ask in the terminal; for a secret, ask the person to save it in Pane under Settings > Secrets.\",\n \"examples\": [\n {\n \"name\": \"Needs a decision\",\n \"ui\": \"approve\",\n \"bad\": [\n {\n \"title\": \"Approve the migration?\",\n \"context\": \"Ready to run 0042_add_index on prod. Proceed?\"\n }\n ],\n \"good\": {\n \"title\": \"Speed up slow search: run the database change?\",\n \"context\": \"Search in the app takes 4 s, and an index brings it under 100 ms. It changes the live database and can't easily be undone. Approve runs it now; Deny keeps it ready for later.\"\n }\n },\n {\n \"name\": \"Blocked\",\n \"ui\": \"options\",\n \"bad\": [\n {\n \"title\": \"Blocked\",\n \"context\": \"CI failing on macos-intel, hdiutil resource busy.\"\n }\n ],\n \"good\": {\n \"title\": \"The 2.5 release is stuck on a GitHub hiccup\",\n \"context\": \"The fixes you asked for pass everywhere except one Mac build machine, which errored. That's GitHub's machine, not our code.\",\n \"choices\": [\n \"Rerun it (about 15 min)\",\n \"Ship without the Intel Mac build\"\n ]\n }\n },\n {\n \"name\": \"Just so you know\",\n \"ui\": \"info\",\n \"bad\": [\n {\n \"title\": \"Done\",\n \"context\": \"Completed refactor of src/notify/queue.ts (head 06d310a).\"\n },\n {\n \"title\": \"PR #812 is ready\",\n \"context\": \"Tests pass.\"\n }\n ],\n \"good\": {\n \"title\": \"Questions from agents now wait until you answer\",\n \"context\": \"The fix you asked for this morning is done and tested. Next is your review: one click to merge.\"\n }\n },\n {\n \"name\": \"A question for ask\",\n \"ui\": \"ask\",\n \"bad\": [\n {\n \"title\": \"Which option?\",\n \"context\": \"1, 2 or 3?\"\n }\n ],\n \"good\": {\n \"title\": \"What should the release headline say?\",\n \"context\": \"You're writing the 2.5 release notes, and the headline is the first line people read. A few words is enough.\"\n }\n }\n ]\n }\n }\n}") diff --git a/packages/runpane/src/generated/contract.ts b/packages/runpane/src/generated/contract.ts index 1e8ddcb0b..d0eb3aa46 100644 --- a/packages/runpane/src/generated/contract.ts +++ b/packages/runpane/src/generated/contract.ts @@ -2548,7 +2548,7 @@ export const RUNPANE_CONTRACT = { { "name": "--context", "value": "<text>", - "description": "notify: background shown under Details." + "description": "notify: the card's subtitle: one or two sentences on why it matters (up to 240 characters)." }, { "name": "--choices", @@ -4140,7 +4140,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -4709,7 +4709,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -6054,7 +6054,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -6577,7 +6577,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -23047,7 +23047,7 @@ export const RUNPANE_CONTRACT = { "name": "--context", "value": "<text>", "required": false, - "description": "Why it matters, shown under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", @@ -23549,7 +23549,7 @@ export const RUNPANE_CONTRACT = { "name": "--context", "value": "<text>", "required": false, - "description": "Background the person can open under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", diff --git a/shared/types/generatedRunpaneContract.ts b/shared/types/generatedRunpaneContract.ts index 1e8ddcb0b..d0eb3aa46 100644 --- a/shared/types/generatedRunpaneContract.ts +++ b/shared/types/generatedRunpaneContract.ts @@ -2548,7 +2548,7 @@ export const RUNPANE_CONTRACT = { { "name": "--context", "value": "<text>", - "description": "notify: background shown under Details." + "description": "notify: the card's subtitle: one or two sentences on why it matters (up to 240 characters)." }, { "name": "--choices", @@ -4140,7 +4140,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -4709,7 +4709,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -6054,7 +6054,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, ask, or info", " --title <text> The question or update in one line, about 80 characters", - " --context <text> Why it matters, shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --questions <json> options, instead of --choices: 2 to 4 of [{\"title\":\"...\",\"choices\":[...]}], one pick each", " --detail <text> approve: the command or SQL to approve", @@ -6577,7 +6577,7 @@ export const RUNPANE_CONTRACT = { "Options:", " --ui <ui> options, approve, or ask", " --title <text> The question in one line", - " --context <text> Background shown under Details", + " --context <text> Subtitle: why it matters, one or two sentences", " --choices <json> options: 1 to 9 of [{\"label\":\"...\",\"detail\":\"...\",\"recommended\":true}]", " --suggestions <json> ask: suggested answers, such as [\"main\",\"release\"]", " --links <json> Up to 6 related files or pages, such as [{\"label\":\"Notes\",\"path\":\"docs/RELEASE.md\"}]", @@ -23047,7 +23047,7 @@ export const RUNPANE_CONTRACT = { "name": "--context", "value": "<text>", "required": false, - "description": "Why it matters, shown under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", @@ -23549,7 +23549,7 @@ export const RUNPANE_CONTRACT = { "name": "--context", "value": "<text>", "required": false, - "description": "Background the person can open under Details." + "description": "The card's subtitle: one or two sentences on why it matters, up to 240 characters." }, { "name": "--choices", From f5f3e110f73942ff4e6de5c0633ddc4edc1c7971 Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 18:46:28 -0800 Subject: [PATCH 10/13] A relay call that ends lets go of the worker's card instead of closing it; name a missing Summary page where the worker meant it --- .../services/notify/notifyQueue.lifecycle.test.ts | 12 ++++++++++++ main/src/services/notify/notifyQueue.ts | 15 ++++++++++++--- main/src/services/notify/notifyService.ts | 6 +++++- main/src/services/notify/summaryPage.test.ts | 1 + 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/main/src/services/notify/notifyQueue.lifecycle.test.ts b/main/src/services/notify/notifyQueue.lifecycle.test.ts index d4110f9ff..9c469cd0f 100644 --- a/main/src/services/notify/notifyQueue.lifecycle.test.ts +++ b/main/src/services/notify/notifyQueue.lifecycle.test.ts @@ -195,6 +195,18 @@ describe('NotifyQueue with its store', () => { expect(item(queue, 'in-1')?.decision).toBe('D-147'); }); + it("a relay call that ends lets go of the worker's card, which keeps waiting and still answers the worker", async () => { + const queue = queueWith(); + queue.addWorkerQuestion({ id: 'in-1', question: 'Q-39: run?', source: worker, at: Date.now() }); + const controller = new AbortController(); + void queue.ask({ relays: 'in-1', question: { ui: 'options', title: 'Q-39: run the script?', choices: [{ label: 'Run', recommended: true }, { label: 'Stop' }] }, source: orchestrator }, controller.signal); + controller.abort(); + expect(item(queue, 'in-1')).toMatchObject({ state: 'waiting', title: 'Q-39: run the script?' }); + queue.run({ type: 'answer', id: 'in-1', answer: { choice: 'Run' } }); + await vi.advanceTimersByTimeAsync(3_000); + expect(typed).toEqual([['w1', 'answer: Q-39 → Run (by you)']]); + }); + it('a relay of an item already ended returns unavailable and posts nothing', async () => { const queue = queueWith(); const result = await queue.ask({ relays: 'nope', question: ask('Q-1: x?'), source: orchestrator }); diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index 890558fbe..12cd476bc 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -1618,8 +1618,15 @@ export class NotifyQueue extends EventEmitter { return new Promise((resolve) => { let returnTimer: Timer | undefined; const onAbort = () => { + if (entry.waiter !== waiter) return; + // A worker's question doesn't belong to the call relaying it: the worker still waits for + // its answer in its tab, so an orchestrator's ended --relays call only lets go of it. + if (entry.item.originKind === 'worker') { + waiter.detach(); + return; + } // Pane stopping its server ends every call; that isn't the asker leaving (J6). - if (entry.waiter === waiter && !this.quitting) this.expire(entry.item.id); + if (!this.quitting) this.expire(entry.item.id); }; const waiter: Waiter = { resolve: (result) => { @@ -1642,7 +1649,8 @@ export class NotifyQueue extends EventEmitter { // Each round has its own 15 minutes: an updated version counts from when it was posted. const remaining = (entry.item.revision?.at ?? entry.item.createdAt) + NOTIFY_RESUME_CAP_MS - Date.now(); returnTimer = setTimeout(() => { - if (remaining <= returnAfterMs) { + const worker = entry.item.originKind === 'worker'; + if (remaining <= returnAfterMs && !worker) { // An answer in its Undo hold reaches this call when the hold ends. if (entry.item.state === 'sent') return; this.expire(entry.item.id, unavailable('Nobody answered within 15 minutes.')); @@ -1654,7 +1662,8 @@ export class NotifyQueue extends EventEmitter { id: entry.item.id, next: `Call \`runpane notify --resume ${entry.item.id}\` now to keep waiting for the answer.`, }); - entry.graceTimer = setTimeout(() => this.expire(entry.item.id), NOTIFY_RESUME_GRACE_MS); + // A relayed worker question keeps waiting for its worker even if the relay never resumes. + if (!worker) entry.graceTimer = setTimeout(() => this.expire(entry.item.id), NOTIFY_RESUME_GRACE_MS); }, Math.min(returnAfterMs, Math.max(remaining, 0))); } }); diff --git a/main/src/services/notify/notifyService.ts b/main/src/services/notify/notifyService.ts index 931efad89..1cc0fff30 100644 --- a/main/src/services/notify/notifyService.ts +++ b/main/src/services/notify/notifyService.ts @@ -795,5 +795,9 @@ function besideStatusFile(statusFile: string, page: string): string { if (parent === folder) break; folder = parent; } - return path.resolve(path.dirname(statusFile), page); + // Found nowhere: name it where the worker most likely meant. "tmp/x.html" beside a status file in + // tmp/ was written from the folder above, so the message names that path. + const folderName = path.basename(path.dirname(statusFile)); + const first = page.split(/[\\/]/u)[0]; + return path.resolve(first === folderName ? path.dirname(path.dirname(statusFile)) : path.dirname(statusFile), page); } diff --git a/main/src/services/notify/summaryPage.test.ts b/main/src/services/notify/summaryPage.test.ts index 09cb16028..3925aa213 100644 --- a/main/src/services/notify/summaryPage.test.ts +++ b/main/src/services/notify/summaryPage.test.ts @@ -59,6 +59,7 @@ describe("an approval's Summary page", () => { fs.mkdirSync(path.join(dir, 'tmp'), { recursive: true }); fs.writeFileSync(path.join(dir, 'tmp', 'summary.html'), '<p>Q-80</p>'); expect(summaryFiles(path.join(dir, 'tmp', 'status.json'), 'tmp/summary.html').summaryPage).toBe(path.join(dir, 'tmp', 'summary.html')); + expect(summaryFiles('/w/tmp/status.json', 'tmp/missing.html').summaryPage).toBe('/w/tmp/missing.html'); expect(summaryFiles(undefined, undefined)).toEqual({}); }); }); From 98a7652d57e812c3dbcde660d9ec271c570f1de8 Mon Sep 17 00:00:00 2001 From: Tyler Brown <tyler@bloomapi.com> Date: Wed, 11 Nov 2026 18:46:57 -0800 Subject: [PATCH 11/13] Find the chat's status line in one pass (React Doctor) --- frontend/src/components/approvals/approvalCard.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/approvals/approvalCard.ts b/frontend/src/components/approvals/approvalCard.ts index 4477e44db..ca9d7c15e 100644 --- a/frontend/src/components/approvals/approvalCard.ts +++ b/frontend/src/components/approvals/approvalCard.ts @@ -233,9 +233,10 @@ export function latestReplied(items: readonly NotifyItem[], sessionId: string, d // An --update clears the reply it answers, so a ready-again approval is found by readyAgain alone. const key = statusKey; return items - .filter(item => (item.reply || item.readyAgain !== undefined) && !dismissed.has(key(item)) && (item.source.session?.id === sessionId || (item.connected?.kind === 'session' && item.connected.id === sessionId))) - .filter(item => (item.state === 'waiting' && (item.reply?.state === 'reading' || item.reply?.state === 'question' || item.reply?.state === 'unread' || item.readyAgain !== undefined)) - || (item.state === 'answered' && item.reply?.state === 'answer')) + .filter(item => (item.reply || item.readyAgain !== undefined) && !dismissed.has(key(item)) + && (item.source.session?.id === sessionId || (item.connected?.kind === 'session' && item.connected.id === sessionId)) + && ((item.state === 'waiting' && (item.reply?.state === 'reading' || item.reply?.state === 'question' || item.reply?.state === 'unread' || item.readyAgain !== undefined)) + || (item.state === 'answered' && item.reply?.state === 'answer'))) .sort((a, b) => (b.readyAgain ?? b.reply?.at ?? 0) - (a.readyAgain ?? a.reply?.at ?? 0))[0]; } From f072b021a0e7dce32b6d5e1be97bea1f59258da5 Mon Sep 17 00:00:00 2001 From: parsakhaz <khazapar7@gmail.com> Date: Wed, 11 Nov 2026 20:43:32 -0800 Subject: [PATCH 12/13] Fix review findings on #1311: a two-question needs_you context keeps its questions, a relay of an old worker question waits a full round, a re-asked card drops a stale Tidied banner --- main/src/ipc/notify.test.ts | 20 +++++++++++++++++++ main/src/ipc/notify.ts | 10 ++++++++-- .../notify/notifyQueue.lifecycle.test.ts | 12 +++++++++++ main/src/services/notify/notifyQueue.ts | 7 +++++-- .../src/services/notify/workerContext.test.ts | 2 +- main/src/services/notify/workerContext.ts | 8 ++++++-- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/main/src/ipc/notify.test.ts b/main/src/ipc/notify.test.ts index 912beca66..152963451 100644 --- a/main/src/ipc/notify.test.ts +++ b/main/src/ipc/notify.test.ts @@ -585,6 +585,26 @@ describe('Approvals items, through the registered handlers', () => { pane.notify.dispose(); }); + it('gives each question of a two-question needs_you context its own buttons, and lists them when the card cannot take buttons', async () => { + const pane = await startWithSessions(); + const caller = { paneId: 'loose', panelId: 'loose-panel' }; + const needsYou = { why: 'The cover sheet needs both.', questions: [ + { q: 'Keep their session?', options: ['Keep it (recommended)', 'Sign them out'] }, + { q: 'Whose sessions end?', why: 'Same as sign-in.', options: ['Everyone (recommended)', 'Members only'] }, + ] }; + void pane.registry.invoke('runpane:notify', [{ ui: 'ask', title: 'Q-5: Okta sign-out', context: JSON.stringify({ state: 'blocked', needs_you: needsYou }), caller }]); + void pane.registry.invoke('runpane:notify', [{ ui: 'approve', title: 'Q-6: Approve the cover sheet?', context: JSON.stringify(needsYou), caller }]); + await flush(); + expect(pane.notify.snapshot().items).toMatchObject([ + { ui: 'options', title: 'Q-5: Okta sign-out', context: 'The cover sheet needs both.', questions: [ + { title: 'Keep their session?', choices: [{ label: 'Keep it', recommended: true }, { label: 'Sign them out' }] }, + { title: 'Whose sessions end?', why: 'Same as sign-in.', choices: [{ label: 'Everyone', recommended: true }, { label: 'Members only' }] }, + ] }, + { ui: 'approve', title: 'Q-6: Approve the cover sheet?', context: '1. Keep their session? (Keep it (recommended) / Sign them out)\n2. Whose sessions end? (Everyone (recommended) / Members only)\nSame as sign-in.\n\nThe cover sheet needs both.' }, + ]); + pane.notify.dispose(); + }); + it('keeps a notify context that is prose starting with a bracket, and reads a status-JSON context on --update too', async () => { const pane = await startWithSessions(); const caller = { paneId: 'loose', panelId: 'loose-panel' }; diff --git a/main/src/ipc/notify.ts b/main/src/ipc/notify.ts index 46b1fc759..beeddf39d 100644 --- a/main/src/ipc/notify.ts +++ b/main/src/ipc/notify.ts @@ -775,12 +775,18 @@ function readStatusContext(request: NotifyRequest, newCard: boolean): void { if (!request.context) return; const card = statusCard(request.context, request.title ?? ''); if (!card) return; + const buttons = newCard && request.ui === 'ask' && !request.suggestions?.length; + // Questions that can't get buttons here are written out in the context instead. + const context = card.questions && !buttons ? card.questionsContext : card.context; // An update without a line for the person clears the old context rather than keeping it. - if (card.context) request.context = card.context; + if (context) request.context = context; else if (newCard) delete request.context; else request.context = ''; if (!newCard) return; - if (card.choices && request.ui === 'ask' && !request.suggestions?.length) { + if (buttons && card.questions) { + request.ui = 'options'; + request.questions = card.questions; + } else if (buttons && card.choices) { request.ui = 'options'; request.choices = card.choices; } diff --git a/main/src/services/notify/notifyQueue.lifecycle.test.ts b/main/src/services/notify/notifyQueue.lifecycle.test.ts index 9c469cd0f..fd29907a2 100644 --- a/main/src/services/notify/notifyQueue.lifecycle.test.ts +++ b/main/src/services/notify/notifyQueue.lifecycle.test.ts @@ -207,6 +207,18 @@ describe('NotifyQueue with its store', () => { expect(typed).toEqual([['w1', 'answer: Q-39 → Run (by you)']]); }); + it('a relay of a worker question asked over 15 minutes ago still waits its full round before returning', async () => { + const queue = queueWith(); + queue.addWorkerQuestion({ id: 'in-1', question: 'Q-39: run?', source: worker, at: Date.now() - 20 * 60_000 }); + let result: unknown; + void queue.ask({ relays: 'in-1', returnAfterMs: 50_000, question: { ui: 'options', title: 'Q-39: run the script?', choices: [{ label: 'Run', recommended: true }, { label: 'Stop' }] }, source: orchestrator }).then((value) => { result = value; }); + await vi.advanceTimersByTimeAsync(49_000); + expect(result).toBeUndefined(); + await vi.advanceTimersByTimeAsync(1_000); + expect(result).toMatchObject({ waiting: true, id: 'in-1' }); + expect(item(queue, 'in-1')).toMatchObject({ state: 'waiting' }); + }); + it('a relay of an item already ended returns unavailable and posts nothing', async () => { const queue = queueWith(); const result = await queue.ask({ relays: 'nope', question: ask('Q-1: x?'), source: orchestrator }); diff --git a/main/src/services/notify/notifyQueue.ts b/main/src/services/notify/notifyQueue.ts index 12cd476bc..6e507c7a0 100644 --- a/main/src/services/notify/notifyQueue.ts +++ b/main/src/services/notify/notifyQueue.ts @@ -341,6 +341,8 @@ export class NotifyQueue extends EventEmitter { if (again) { // The same tab asks the same thing (after a restart, say): it joins the card already there (J19). Object.assign(again.item, question); + // A card sent well formed this time is no longer one Pane tidied. + if (!question.asSent) delete again.item.asSent; again.restored = false; this.save(again); const result = this.wait(again, request.returnAfterMs, signal); @@ -1646,7 +1648,8 @@ export class NotifyQueue extends EventEmitter { } signal?.addEventListener('abort', onAbort, { once: true }); if (returnAfterMs !== undefined && returnAfterMs > 0) { - // Each round has its own 15 minutes: an updated version counts from when it was posted. + // Each round has its own 15 minutes: an updated version counts from when it was posted. A + // worker's question has no such limit, so each of its relay calls waits a full round. const remaining = (entry.item.revision?.at ?? entry.item.createdAt) + NOTIFY_RESUME_CAP_MS - Date.now(); returnTimer = setTimeout(() => { const worker = entry.item.originKind === 'worker'; @@ -1664,7 +1667,7 @@ export class NotifyQueue extends EventEmitter { }); // A relayed worker question keeps waiting for its worker even if the relay never resumes. if (!worker) entry.graceTimer = setTimeout(() => this.expire(entry.item.id), NOTIFY_RESUME_GRACE_MS); - }, Math.min(returnAfterMs, Math.max(remaining, 0))); + }, entry.item.originKind === 'worker' ? returnAfterMs : Math.min(returnAfterMs, Math.max(remaining, 0))); } }); } diff --git a/main/src/services/notify/workerContext.test.ts b/main/src/services/notify/workerContext.test.ts index 783e991bc..ec733b986 100644 --- a/main/src/services/notify/workerContext.test.ts +++ b/main/src/services/notify/workerContext.test.ts @@ -51,7 +51,7 @@ describe("a card built from a worker's status JSON", () => { it('gives each of several questions its own buttons, with the top-level why as the subtitle', () => { const card = statusCard(JSON.stringify({ needs_you: { why: 'Invoices ship Friday.', questions: [{ q: 'Id or number?', options: ['Id (recommended)', 'Number'] }, { q: 'Keep old PDFs?', why: 'They are linked from emails.', options: ['Yes', 'No'] }] } }), 'Two invoice questions'); - expect(card).toEqual({ + expect(card).toMatchObject({ context: 'Invoices ship Friday.', questions: [ { title: 'Id or number?', choices: [{ label: 'Id', recommended: true }, { label: 'Number' }] }, diff --git a/main/src/services/notify/workerContext.ts b/main/src/services/notify/workerContext.ts index a58ebe5ac..64b4bfc5d 100644 --- a/main/src/services/notify/workerContext.ts +++ b/main/src/services/notify/workerContext.ts @@ -32,6 +32,8 @@ export interface StatusCard { choices?: NotifyChoice[]; /** Several questions, each with its own buttons, when every one has options to press. */ questions?: NotifySubQuestion[]; + /** The context with those questions written out, for a card that can't show their buttons. */ + questionsContext?: string; links?: NotifyLink[]; /** The asker's own Summary page (`summary` in needs_you), as written: maybe relative. */ summaryPage?: string; @@ -160,8 +162,10 @@ function cardOf(needsYou: NeedsYou, title: string): StatusCard { if (why) sub.why = why; return sub; }); - if (subQuestions.every((sub): sub is NotifySubQuestion => sub !== undefined)) card.questions = subQuestions; - else parts.push(listed(questions)); + if (subQuestions.every((sub): sub is NotifySubQuestion => sub !== undefined)) { + card.questions = subQuestions; + card.questionsContext = cut([listed(questions), needsYou.why?.trim()].filter(Boolean).join('\n\n')); + } else parts.push(listed(questions)); } else if (questions.length > 1) { // Some question has nothing to press: they are listed, and answered in words. parts.push(listed(questions)); From aaab9948cb3986a23e20214129b817ccf3e804b2 Mon Sep 17 00:00:00 2001 From: parsakhaz <khazapar7@gmail.com> Date: Wed, 11 Nov 2026 20:50:33 -0800 Subject: [PATCH 13/13] Resolve the expected Summary paths per platform so summaryPage.test passes on Windows --- main/src/services/notify/summaryPage.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/services/notify/summaryPage.test.ts b/main/src/services/notify/summaryPage.test.ts index 3925aa213..8da7a39b4 100644 --- a/main/src/services/notify/summaryPage.test.ts +++ b/main/src/services/notify/summaryPage.test.ts @@ -54,12 +54,12 @@ describe("an approval's Summary page", () => { it("takes a worker's .html --summary-file as its page, and any other file as its status file", () => { expect(summaryFiles('/w/Q-80-summary.html', undefined)).toEqual({ summaryPage: '/w/Q-80-summary.html' }); - expect(summaryFiles('/w/status.json', 'pages/summary.html')).toEqual({ statusFile: '/w/status.json', summaryPage: '/w/pages/summary.html' }); + expect(summaryFiles('/w/status.json', 'pages/summary.html')).toEqual({ statusFile: '/w/status.json', summaryPage: path.resolve('/w/pages/summary.html') }); // Written from the worker's folder (the worktree), with the status file in tmp/: found one folder up. fs.mkdirSync(path.join(dir, 'tmp'), { recursive: true }); fs.writeFileSync(path.join(dir, 'tmp', 'summary.html'), '<p>Q-80</p>'); expect(summaryFiles(path.join(dir, 'tmp', 'status.json'), 'tmp/summary.html').summaryPage).toBe(path.join(dir, 'tmp', 'summary.html')); - expect(summaryFiles('/w/tmp/status.json', 'tmp/missing.html').summaryPage).toBe('/w/tmp/missing.html'); + expect(summaryFiles('/w/tmp/status.json', 'tmp/missing.html').summaryPage).toBe(path.resolve('/w/tmp/missing.html')); expect(summaryFiles(undefined, undefined)).toEqual({}); }); });