# Remote ask events

pi-ask exposes a local `pi.events` contract for trusted Pi extensions that run in the same Pi process.

Use this for local bridges: status cards, desktop helpers, or approval UIs. Do not use terminal keystroke automation. pi-ask does not expose a network API.

## Channels

Lifecycle:

- `@geoqiao/pi-ask:started`
- `@geoqiao/pi-ask:completed`

Remote submit:

- `@geoqiao/pi-ask:submit`
- `@geoqiao/pi-ask:submit-result`

## Started

Emitted after a validated TUI ask flow opens. The RPC portable-dialog fallback does not start the remote event bridge or emit lifecycle events.

```ts
type PiAskStartedEvent = {
  version: 1;
  flowId: string;
  toolCallId?: string;
  source: "tool" | "answer" | "answer:again" | "ask:replay" | "ask:resume";
  title?: string;
  questions: AskQuestion[];
  createdAt: number;
};
```

Use `flowId` for submit/correlation. Use `questions[].id` and `questions[].options[].value` for answers. `ask:resume` identifies a form recovered from an interrupted tool call. Started-event options preserve the optional `recommended` boolean as presentation metadata only.

## Submit an answer

```ts
pi.events.emit("@geoqiao/pi-ask:submit", {
  version: 1,
  requestId: `bridge-${Date.now()}`,
  flowId,
  response: {
    kind: "answer",
    mode: "submit",
    answers: {
      questionId: { values: ["option-value"] },
    },
  },
});
```

Answer shape:

```ts
type PiAskRemoteAnswer = {
  values?: string[];
  customText?: string;
  note?: string;
  optionNotes?: Record<string, string>;
};
```

Rules:

- `values` must match option `value`s from the started event
- keys in `answers` must match question ids
- labels and indices are recomputed by pi-ask
- a remote `answer` replaces the current answer set; stale UI answers are not merged
- `mode` defaults to `"submit"`; use `"elaborate"` to complete as an elaboration request

## Cancel

```ts
pi.events.emit("@geoqiao/pi-ask:submit", {
  version: 1,
  requestId: `bridge-${Date.now()}`,
  flowId,
  response: { kind: "cancel" },
});
```

Cancel must be explicit. pi-ask does not infer cancel/approve/deny from labels or button names.

## Submit result

After a submit request, pi-ask emits:

```ts
type PiAskSubmitResultEvent =
  | { version: 1; requestId: string; flowId: string; ok: true }
  | {
      version: 1;
      requestId: string;
      flowId: string;
      ok: false;
      error: "flow_not_found" | "invalid_request" | "invalid_answer";
      message: string;
    };
```

Correlate by `requestId` and `flowId`. Do not depend on strict ordering between `submit-result` and `completed`.

## Completed

Emitted when the TUI flow resolves. RPC portable-dialog completion is returned through the normal tool result instead.

```ts
type PiAskCompletedEvent = {
  version: 1;
  flowId: string;
  toolCallId?: string;
  source: "tool" | "answer" | "answer:again" | "ask:replay" | "ask:resume";
  result: AskResult;
  completedAt: number;
};
```

## Minimal bridge

```ts
export default function piAskBridge(pi: any) {
  pi.events.on("@geoqiao/pi-ask:started", (event: any) => {
    const question = event.questions[0];
    const option = question.options[0];

    pi.events.emit("@geoqiao/pi-ask:submit", {
      version: 1,
      requestId: `bridge-${Date.now()}`,
      flowId: event.flowId,
      response: {
        kind: "answer",
        answers: {
          [question.id]: { values: [option.value] },
        },
      },
    });
  });

  pi.events.on("@geoqiao/pi-ask:submit-result", (event: any) => {
    if (!event.ok) console.error(event.error, event.message);
  });
}
```

Third-party integrations own their own UI policy and mappings. For example, a bridge may map a button to `{ values: ["yes"] }`, but pi-ask will never guess that mapping from the label.

## Local smoke test

Create a temporary bridge and run pi with only this repo extension plus the bridge:

```bash
cat > /tmp/pi-ask-smoke.ts <<'EOF'
export default function smoke(pi: any) {
  pi.events.on("@geoqiao/pi-ask:started", (event: any) => {
    const q = event.questions[0];
    setTimeout(() => {
      pi.events.emit("@geoqiao/pi-ask:submit", {
        version: 1,
        requestId: `smoke-${Date.now()}`,
        flowId: event.flowId,
        response: { kind: "answer", answers: { [q.id]: { values: ["tool"] } } },
      });
    }, 2500);
  });
}
EOF

pi \
  --no-extensions \
  --no-skills \
  --no-prompt-templates \
  --no-themes \
  --no-context-files \
  -e "$PWD/src/index.ts" \
  -e /tmp/pi-ask-smoke.ts \
  --skill "$PWD/skills/ask-user"
```

Then ask Pi:

```txt
Use ask_user. Title: pi-ask smoke. Ask one single-select question id tool with options tool and nope.
```

The ask UI should open and auto-submit `tool` after about 2.5 seconds.
