4.4 KiB
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:
@eko24ive/pi-ask:started@eko24ive/pi-ask:completed
Remote submit:
@eko24ive/pi-ask:submit@eko24ive/pi-ask:submit-result
Started
Emitted after a validated ask UI flow opens.
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.
Submit an answer
pi.events.emit("@eko24ive/pi-ask:submit", {
version: 1,
requestId: `bridge-${Date.now()}`,
flowId,
response: {
kind: "answer",
mode: "submit",
answers: {
questionId: { values: ["option-value"] },
},
},
});
Answer shape:
type PiAskRemoteAnswer = {
values?: string[];
customText?: string;
note?: string;
optionNotes?: Record<string, string>;
};
Rules:
valuesmust match optionvalues from the started event- keys in
answersmust match question ids - labels and indices are recomputed by pi-ask
- a remote
answerreplaces the current answer set; stale UI answers are not merged modedefaults to"submit"; use"elaborate"to complete as an elaboration request
Cancel
pi.events.emit("@eko24ive/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:
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 flow resolves.
type PiAskCompletedEvent = {
version: 1;
flowId: string;
toolCallId?: string;
source: "tool" | "answer" | "answer:again" | "ask:replay" | "ask:resume";
result: AskResult;
completedAt: number;
};
Minimal bridge
export default function piAskBridge(pi: any) {
pi.events.on("@eko24ive/pi-ask:started", (event: any) => {
const question = event.questions[0];
const option = question.options[0];
pi.events.emit("@eko24ive/pi-ask:submit", {
version: 1,
requestId: `bridge-${Date.now()}`,
flowId: event.flowId,
response: {
kind: "answer",
answers: {
[question.id]: { values: [option.value] },
},
},
});
});
pi.events.on("@eko24ive/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:
cat > /tmp/pi-ask-smoke.ts <<'EOF'
export default function smoke(pi: any) {
pi.events.on("@eko24ive/pi-ask:started", (event: any) => {
const q = event.questions[0];
setTimeout(() => {
pi.events.emit("@eko24ive/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:
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.