+3
-1
@@ -5,7 +5,8 @@ This folder keeps only the documentation needed to understand and maintain the e
|
||||
## Files
|
||||
|
||||
- `configuration.md` — source of truth for configuring pi-ask keymaps, behaviour, and `/answer` extraction
|
||||
- `contract.md` — external behavior, full tool payload/result details, and UX guarantees
|
||||
- `contract.md` — external behavior, tool payload/result details, and UX guarantees
|
||||
- `remote-events.md` — local inter-extension event contract, bridge examples, and smoke-test steps
|
||||
- `architecture.md` — module boundaries and invariants
|
||||
- `../skills/ask-user/SKILL.md` — auto-bundled agent-side decision-gate guidance; enabled by default, but can be disabled via `pi config`; inspired by https://github.com/edlsh/pi-ask-user
|
||||
|
||||
@@ -13,6 +14,7 @@ This folder keeps only the documentation needed to understand and maintain the e
|
||||
|
||||
- start with `configuration.md` for config-editing rules
|
||||
- read `contract.md` for behavior
|
||||
- read `remote-events.md` for local bridge/event integration
|
||||
- read `architecture.md` for code layout
|
||||
|
||||
## Local test run
|
||||
|
||||
@@ -19,6 +19,7 @@ The codebase is split so the implementation reads through file boundaries and na
|
||||
- `src/answer-extraction.ts` — configured extraction model selection and raw-JSON extraction retries
|
||||
- `src/ask-payload-store.ts` — branch-aware persisted ask payload lookup
|
||||
- `src/notifications.ts` — best-effort ask notification payload rendering and channel execution
|
||||
- `src/remote-ask.ts` — package-prefixed local event contract, active-flow registry, and explicit remote submission validation
|
||||
- `src/schema.ts` — TypeBox schema
|
||||
- `src/types.ts` — shared types
|
||||
|
||||
@@ -86,6 +87,7 @@ The codebase is split so the implementation reads through file boundaries and na
|
||||
- legacy root config files are read as a fallback only when the current config file is absent; disk is left untouched
|
||||
- invalid config files are left untouched; defaults are loaded for the session with a notice
|
||||
- live config updates can affect an in-progress ask flow immediately
|
||||
- remote ask submissions must be explicit `answer` or `cancel` responses; pi-ask validates ids/values but never infers approve/deny semantics from labels or option values
|
||||
|
||||
## Documentation rule
|
||||
|
||||
|
||||
@@ -267,6 +267,21 @@ The ask flow subscribes to runtime settings updates while open. In practice, thi
|
||||
|
||||
When enabled, pi-ask emits one best-effort external notification per ask session after the ask UI opens and waits for input. The default title is `pi ask`; the message is `Question waiting: <label or prompt>`. Channels run in configured order and failures never fail or cancel the ask flow.
|
||||
|
||||
## Remote inter-extension events
|
||||
|
||||
pi-ask exposes a local `pi.events` contract for trusted Pi extensions. It does not expose a network API and does not automate terminal keystrokes.
|
||||
|
||||
Channels:
|
||||
|
||||
- `@eko24ive/pi-ask:started`
|
||||
- `@eko24ive/pi-ask:completed`
|
||||
- `@eko24ive/pi-ask:submit`
|
||||
- `@eko24ive/pi-ask:submit-result`
|
||||
|
||||
Remote submissions must be explicit `{ kind: "answer" }` or `{ kind: "cancel" }` responses. Remote answers use question ids and normalized option values from the started event. pi-ask validates ids and values, recomputes labels/indices, and does not infer approval semantics from labels.
|
||||
|
||||
See [`remote-events.md`](remote-events.md) for payload shapes, examples, and a local smoke test.
|
||||
|
||||
## Slash command replay/extraction
|
||||
|
||||
- valid `ask_user` payloads are persisted as branch custom entries before the UI opens, so `/ask:replay` can reopen them after cancel, `/resume`, or `/tree`
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# 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.
|
||||
|
||||
```ts
|
||||
type PiAskStartedEvent = {
|
||||
version: 1;
|
||||
flowId: string;
|
||||
toolCallId?: string;
|
||||
source: "tool" | "answer" | "answer:again" | "ask:replay";
|
||||
title?: string;
|
||||
questions: AskQuestion[];
|
||||
createdAt: number;
|
||||
};
|
||||
```
|
||||
|
||||
Use `flowId` for submit/correlation. Use `questions[].id` and `questions[].options[].value` for answers.
|
||||
|
||||
## Submit an answer
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```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("@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:
|
||||
|
||||
```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 flow resolves.
|
||||
|
||||
```ts
|
||||
type PiAskCompletedEvent = {
|
||||
version: 1;
|
||||
flowId: string;
|
||||
toolCallId?: string;
|
||||
source: "tool" | "answer" | "answer:again" | "ask:replay";
|
||||
result: AskResult;
|
||||
completedAt: number;
|
||||
};
|
||||
```
|
||||
|
||||
## Minimal bridge
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user