@howaboua/pi-ask 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @howaboua/pi-ask
2
2
 
3
+ ## 0.0.11
4
+
5
+ - Parents can receive and answer asynchronous questions through Shepherdr while workers continue.
6
+
3
7
  ## 0.0.10
4
8
 
5
9
  - Requires Pi 1.0.0 or later.
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import type { AskPrompt, PendingAsk } from "./contracts.js";
2
+ import type { AskPrompt, AskResponse, PendingAsk } from "./contracts.js";
3
3
  import { normalizeResponses, summarizeResponses } from "./normalize.js";
4
4
  import type { PendingAskUpdate } from "./pending.js";
5
5
  import { askWithPiUi } from "./pi-ui.js";
@@ -14,6 +14,7 @@ export interface AskCoordinatorOptions {
14
14
  askInComposer?: AskInComposer;
15
15
  deliverSteer?: (message: string) => void;
16
16
  onPendingChange?: (update: PendingAskUpdate) => void;
17
+ onSteerActiveChange?: (request: PendingAsk, active: boolean) => void;
17
18
  }
18
19
 
19
20
  interface PresentAskOptions {
@@ -34,11 +35,13 @@ export function createAskCoordinator({
34
35
  askInComposer,
35
36
  deliverSteer,
36
37
  onPendingChange,
38
+ onSteerActiveChange,
37
39
  }: AskCoordinatorOptions = {}) {
38
40
  let generation = 0;
39
41
  let sessionAbort = new AbortController();
40
42
  let presentationTail = Promise.resolve();
41
43
  const activeSteers = new Set<string>();
44
+ let presentedSteer: PendingAsk | undefined;
42
45
 
43
46
  const present = (
44
47
  ctx: ExtensionContext,
@@ -95,10 +98,19 @@ export function createAskCoordinator({
95
98
  }
96
99
  };
97
100
 
98
- const close = (ctx: ExtensionContext, id: string) => {
101
+ const close = (
102
+ ctx: ExtensionContext,
103
+ id: string,
104
+ responses?: AskResponse[],
105
+ ) => {
99
106
  activeSteers.delete(id);
100
107
  try {
101
- onPendingChange?.({ version: 1, state: "closed", id });
108
+ onPendingChange?.({
109
+ version: 1,
110
+ state: "closed",
111
+ id,
112
+ ...(responses ? { responses } : { dismissed: true }),
113
+ });
102
114
  } catch (error) {
103
115
  notifyFailure(ctx, error);
104
116
  }
@@ -123,12 +135,22 @@ export function createAskCoordinator({
123
135
  void present(ctx, request.prompts, {
124
136
  steering: true,
125
137
  signal: sessionAbort.signal,
138
+ onActiveChange: (active) => {
139
+ if (requestGeneration === generation && !sessionAbort.signal.aborted) {
140
+ presentedSteer = active ? request : undefined;
141
+ onSteerActiveChange?.(request, active);
142
+ }
143
+ },
126
144
  }).then(
127
145
  (responses) => {
128
146
  if (requestGeneration !== generation || sessionAbort.signal.aborted)
129
147
  return;
130
148
  if (deliver(ctx, steerResponse(request.prompts, responses)))
131
- close(ctx, request.id);
149
+ close(
150
+ ctx,
151
+ request.id,
152
+ normalizeResponses(request.prompts, responses) ?? undefined,
153
+ );
132
154
  },
133
155
  (error: unknown) => {
134
156
  if (requestGeneration !== generation || sessionAbort.signal.aborted)
@@ -153,6 +175,7 @@ export function createAskCoordinator({
153
175
  },
154
176
  restorePending(pending: readonly PendingAsk[], ctx: ExtensionContext) {
155
177
  generation++;
178
+ presentedSteer = undefined;
156
179
  sessionAbort.abort();
157
180
  sessionAbort = new AbortController();
158
181
  presentationTail = Promise.resolve();
@@ -161,9 +184,14 @@ export function createAskCoordinator({
161
184
  for (const request of pending) startSteer(request, ctx, false);
162
185
  },
163
186
  shutdown() {
164
- generation++;
165
- sessionAbort.abort();
166
- activeSteers.clear();
187
+ try {
188
+ if (presentedSteer) onSteerActiveChange?.(presentedSteer, false);
189
+ } finally {
190
+ presentedSteer = undefined;
191
+ generation++;
192
+ sessionAbort.abort();
193
+ activeSteers.clear();
194
+ }
167
195
  },
168
196
  get sessionSignal(): AbortSignal {
169
197
  return sessionAbort.signal;
package/ask/pending.ts CHANGED
@@ -1,12 +1,18 @@
1
1
  import type { SessionEntry } from "@earendil-works/pi-coding-agent";
2
- import type { AskPrompt, PendingAsk } from "./contracts.js";
2
+ import type { AskPrompt, AskResponse, PendingAsk } from "./contracts.js";
3
3
  import { normalizeAskInput } from "./normalize.js";
4
4
 
5
5
  export const PENDING_ASK_ENTRY_TYPE = "pi-ask-pending";
6
6
 
7
7
  export type PendingAskUpdate =
8
8
  | { version: 1; state: "pending"; id: string; prompts: AskPrompt[] }
9
- | { version: 1; state: "closed"; id: string };
9
+ | {
10
+ version: 1;
11
+ state: "closed";
12
+ id: string;
13
+ responses?: AskResponse[];
14
+ dismissed?: boolean;
15
+ };
10
16
 
11
17
  function isRecord(value: unknown): value is Record<string, unknown> {
12
18
  return typeof value === "object" && value !== null;
package/ask/tool.ts CHANGED
@@ -33,11 +33,13 @@ export function createAskRuntime({
33
33
  deliverSteer,
34
34
  onBlockedChange,
35
35
  onPendingChange,
36
+ onSteerActiveChange,
36
37
  }: AskToolOptions = {}) {
37
38
  const coordinator = createAskCoordinator({
38
39
  ...(askInComposer ? { askInComposer } : {}),
39
40
  ...(deliverSteer ? { deliverSteer } : {}),
40
41
  ...(onPendingChange ? { onPendingChange } : {}),
42
+ ...(onSteerActiveChange ? { onSteerActiveChange } : {}),
41
43
  });
42
44
 
43
45
  const tool = defineTool({
@@ -70,7 +72,7 @@ export function createAskRuntime({
70
72
  return {
71
73
  content: [
72
74
  textContent(
73
- "Question presented. Continue working; the response will arrive as steering.",
75
+ "Question queued. Continue working; the response will arrive as steering.",
74
76
  ),
75
77
  ],
76
78
  details: { kind: "prompt", pending: true, id: toolCallId },
package/index.ts CHANGED
@@ -62,6 +62,20 @@ export default async function humanInTheLoop(pi: ExtensionAPI): Promise<void> {
62
62
  onPendingChange: (update) => {
63
63
  pi.appendEntry(PENDING_ASK_ENTRY_TYPE, update);
64
64
  },
65
+ onSteerActiveChange: (request, active) => {
66
+ pi.appendEntry(
67
+ "pi-ask-active",
68
+ active
69
+ ? {
70
+ version: 1,
71
+ state: "active",
72
+ id: request.id,
73
+ delivery: "steer",
74
+ prompts: request.prompts,
75
+ }
76
+ : { version: 1, state: "closed", id: request.id },
77
+ );
78
+ },
65
79
  });
66
80
  const ask = askRuntime.tool;
67
81
  const restorePending = (ctx: ExtensionContext) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/pi-ask",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Interactive decisions, review triage, and human handoffs for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  ]
46
46
  },
47
47
  "peerDependencies": {
48
- "@howaboua/pi-codex-conversion": ">=3.0.43",
48
+ "@howaboua/pi-codex-conversion": ">=3.0.46",
49
49
  "@earendil-works/pi-ai": ">=1.0.0",
50
50
  "@earendil-works/pi-coding-agent": ">=1.0.0",
51
51
  "@earendil-works/pi-tui": ">=1.0.0",
@@ -57,11 +57,11 @@
57
57
  }
58
58
  },
59
59
  "devDependencies": {
60
- "@howaboua/pi-codex-conversion": "3.0.43",
60
+ "@howaboua/pi-codex-conversion": "3.0.46",
61
61
  "@biomejs/biome": "^2.5.10",
62
- "@earendil-works/pi-ai": "1.0.0",
63
- "@earendil-works/pi-coding-agent": "1.0.0",
64
- "@earendil-works/pi-tui": "1.0.0",
62
+ "@earendil-works/pi-ai": "1.0.4",
63
+ "@earendil-works/pi-coding-agent": "1.0.4",
64
+ "@earendil-works/pi-tui": "1.0.4",
65
65
  "typebox": "1.3.18",
66
66
  "typescript": "^7.0.2"
67
67
  },