@zhuxixi/pi-agent-board 0.4.0 → 0.4.1
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.
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Question/Questionnaire Tool Grouping Fix Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make pi sessions blocked on the `question`/`questionnaire` tools group under "Needs answer" instead of "Running".
|
|
6
|
+
|
|
7
|
+
**Architecture:** Two minimal changes to `src/core/events.mjs`: (1) extend the private `questionFromArgs` helper to read pi's arg shapes; (2) replace the hardcoded `=== "ask_questions"` name checks with a `QUESTION_TOOL_NAMES` set. All downstream behavior (needs_input state, grouping, summary) already exists via `preservePendingQuestion` and `deriveSummary` — verified in spec section 3.3, nothing else changes.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js (`.mjs` ESM, no deps), `node:test` + `node:assert/strict` for tests.
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- All code changes confined to `src/core/events.mjs` and `test/events.test.mjs`.
|
|
14
|
+
- No store/schema/type changes; no changes to `finalizeRun`, `projectViewState`, `deriveSummary`, `rows.mjs`, `service.mjs`.
|
|
15
|
+
- Non-interactive (detached) reduction path keeps its current behavior — the `opts.interactive` gate stays.
|
|
16
|
+
- Static name set, no config surface.
|
|
17
|
+
- Commit messages in conventional commits format; stage files individually (`git add <file>`), never `git add -A`.
|
|
18
|
+
- Test command: `node --test test/events.test.mjs`; project gate: `npm run verify`.
|
|
19
|
+
- Work in worktree `/home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-26-question-tool-grouping`; never touch main.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Task 1: Extend `questionFromArgs` for pi arg shapes
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Modify: `src/core/events.mjs:229-235`
|
|
27
|
+
- Test: `test/events.test.mjs` (append new test near the ask_questions tests, after line 160)
|
|
28
|
+
|
|
29
|
+
**Interfaces:**
|
|
30
|
+
- Consumes: nothing new.
|
|
31
|
+
- Produces: `questionFromArgs(args)` (private) returns the first non-empty question text from: `args.question` (string), or `args.questions[]` items' `question` or `prompt` fields; falls back to `"Answer the pending question"`. Task 2's name-recognition relies on this extraction.
|
|
32
|
+
|
|
33
|
+
- [ ] **Step 1: Write the failing test**
|
|
34
|
+
|
|
35
|
+
Append to `test/events.test.mjs` (after the "interactive questions remain visible..." test):
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
test("questionFromArgs extracts pi question/questionnaire arg shapes", () => {
|
|
39
|
+
// pi `question` tool shape: args.question is a plain string.
|
|
40
|
+
const s1 = createRunStatus(cfg(), 1, 1000);
|
|
41
|
+
reduceEvent(s1, {
|
|
42
|
+
type: "tool_execution_start",
|
|
43
|
+
toolCallId: "q1",
|
|
44
|
+
toolName: "ask_questions",
|
|
45
|
+
args: { question: "Approve the plan?" },
|
|
46
|
+
}, 2000, { interactive: true });
|
|
47
|
+
assert.equal(s1.question, "Approve the plan?");
|
|
48
|
+
|
|
49
|
+
// pi `questionnaire` tool shape: args.questions[].prompt.
|
|
50
|
+
const s2 = createRunStatus(cfg(), 1, 1000);
|
|
51
|
+
reduceEvent(s2, {
|
|
52
|
+
type: "tool_execution_start",
|
|
53
|
+
toolCallId: "q1",
|
|
54
|
+
toolName: "ask_questions",
|
|
55
|
+
args: { questions: [{ prompt: "Pick the scope?" }] },
|
|
56
|
+
}, 2000, { interactive: true });
|
|
57
|
+
assert.equal(s2.question, "Pick the scope?");
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Note: the tests route through the reducer with the already-recognized `ask_questions` name because `questionFromArgs` is private and the reducer is the public surface; name recognition for the pi tools lands in Task 2.
|
|
62
|
+
|
|
63
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
64
|
+
|
|
65
|
+
Run: `node --test test/events.test.mjs`
|
|
66
|
+
Expected: FAIL — both assertions get `"Answer the pending question"` (the current extractor only reads `questions[].question`).
|
|
67
|
+
|
|
68
|
+
- [ ] **Step 3: Implement the extractor**
|
|
69
|
+
|
|
70
|
+
Replace the body of `questionFromArgs` in `src/core/events.mjs` (L229-235):
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
function questionFromArgs(args) {
|
|
74
|
+
if (typeof args?.question === "string" && args.question.trim()) return args.question.trim();
|
|
75
|
+
for (const item of Array.isArray(args?.questions) ? args.questions : []) {
|
|
76
|
+
const question = String(item?.question ?? item?.prompt ?? "").trim();
|
|
77
|
+
if (question) return question;
|
|
78
|
+
}
|
|
79
|
+
return "Answer the pending question";
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
84
|
+
|
|
85
|
+
Run: `node --test test/events.test.mjs`
|
|
86
|
+
Expected: PASS (all 17 existing + 1 new test).
|
|
87
|
+
|
|
88
|
+
- [ ] **Step 5: Commit**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git add src/core/events.mjs test/events.test.mjs
|
|
92
|
+
git commit -m "fix: support pi question arg shapes in questionFromArgs (issue #26)"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Task 2: Recognize `question`/`questionnaire` as pending-question tools
|
|
98
|
+
|
|
99
|
+
**Files:**
|
|
100
|
+
- Modify: `src/core/events.mjs:25-27` (insert set), `src/core/events.mjs:83`, `src/core/events.mjs:97`
|
|
101
|
+
- Test: `test/events.test.mjs` (append three tests after the Task 1 test)
|
|
102
|
+
|
|
103
|
+
**Interfaces:**
|
|
104
|
+
- Consumes: `questionFromArgs` extraction from Task 1.
|
|
105
|
+
- Produces: `QUESTION_TOOL_NAMES` (module-private `Set<string>`). `reduceEvent` treats any interactive tool_execution_start/end whose tool name is in the set as a pending-question event — `upsertPendingQuestion`/`removePendingQuestion` plus the existing `preservePendingQuestion` flow (needs_input state, `currentTool = null`).
|
|
106
|
+
|
|
107
|
+
- [ ] **Step 1: Write the failing tests**
|
|
108
|
+
|
|
109
|
+
Append to `test/events.test.mjs`:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
test("interactive pi question tool is treated as a pending question", () => {
|
|
113
|
+
const s = createRunStatus(cfg(), 1, 1000);
|
|
114
|
+
reduceEvent(s, {
|
|
115
|
+
type: "tool_execution_start",
|
|
116
|
+
toolCallId: "q1",
|
|
117
|
+
toolName: "question",
|
|
118
|
+
args: { question: "Approve the plan?", options: [{ label: "Yes" }, { label: "No" }] },
|
|
119
|
+
}, 2000, { interactive: true });
|
|
120
|
+
assert.equal(s.semanticState, "needs_input");
|
|
121
|
+
assert.equal(s.question, "Approve the plan?");
|
|
122
|
+
assert.equal(s.currentTool, null);
|
|
123
|
+
assert.equal(s.summary, "Approve the plan?");
|
|
124
|
+
assert.deepEqual(s.pendingQuestions, [{ toolCallId: "q1", question: "Approve the plan?" }]);
|
|
125
|
+
assert.equal(projectViewState(s, 2100).needsInput, true);
|
|
126
|
+
|
|
127
|
+
reduceEvent(s, { type: "tool_execution_end", toolCallId: "q1", toolName: "question", isError: false }, 2400, { interactive: true });
|
|
128
|
+
assert.equal(s.semanticState, "working");
|
|
129
|
+
assert.equal(s.question, null);
|
|
130
|
+
assert.deepEqual(s.pendingQuestions, []);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("interactive questionnaire tool extracts prompt and clears on end", () => {
|
|
134
|
+
const s = createRunStatus(cfg(), 1, 1000);
|
|
135
|
+
reduceEvent(s, {
|
|
136
|
+
type: "tool_execution_start",
|
|
137
|
+
toolCallId: "q1",
|
|
138
|
+
toolName: "questionnaire",
|
|
139
|
+
args: { questions: [{ prompt: "Pick the scope?" }] },
|
|
140
|
+
}, 2000, { interactive: true });
|
|
141
|
+
assert.equal(s.semanticState, "needs_input");
|
|
142
|
+
assert.equal(s.question, "Pick the scope?");
|
|
143
|
+
reduceEvent(s, { type: "tool_execution_end", toolCallId: "q1", toolName: "questionnaire", isError: false }, 2400, { interactive: true });
|
|
144
|
+
assert.equal(s.semanticState, "working");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("detached question tool keeps legacy currentTool behavior", () => {
|
|
148
|
+
const s = createRunStatus(cfg(), 1, 1000);
|
|
149
|
+
reduceEvent(s, { type: "tool_execution_start", toolCallId: "q1", toolName: "question", args: { question: "Approve?" } }, 2000);
|
|
150
|
+
assert.equal(s.semanticState, "working");
|
|
151
|
+
assert.equal(s.currentTool.name, "question");
|
|
152
|
+
assert.deepEqual(s.pendingQuestions, []);
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
157
|
+
|
|
158
|
+
Run: `node --test test/events.test.mjs`
|
|
159
|
+
Expected: FAIL — question/questionnaire names are not recognized: first test asserts `needs_input` but gets `working` with `currentTool.name === "question"`.
|
|
160
|
+
|
|
161
|
+
- [ ] **Step 3: Add the name set and wire the two checks**
|
|
162
|
+
|
|
163
|
+
Insert after the imports at the top of `src/core/events.mjs` (before `createRunStatus`, ~L25):
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
/** Tool names whose interactive execution blocks on a user answer. */
|
|
167
|
+
const QUESTION_TOOL_NAMES = new Set(["ask_questions", "question", "questionnaire"]);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Replace L83:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
if (opts.interactive && QUESTION_TOOL_NAMES.has(name)) {
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Replace L97:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
if (opts.interactive && QUESTION_TOOL_NAMES.has(event.toolName ?? "")) removePendingQuestion(status, event.toolCallId);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
183
|
+
|
|
184
|
+
Run: `node --test test/events.test.mjs`
|
|
185
|
+
Expected: PASS (21 tests total: 17 existing + 1 Task 1 + 3 new). Existing `ask_questions` tests must pass unchanged.
|
|
186
|
+
|
|
187
|
+
- [ ] **Step 5: Full project gate**
|
|
188
|
+
|
|
189
|
+
Run: `npm run verify`
|
|
190
|
+
Expected: typecheck, all tests, and pack dry-run pass.
|
|
191
|
+
|
|
192
|
+
- [ ] **Step 6: Commit**
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
git add src/core/events.mjs test/events.test.mjs
|
|
196
|
+
git commit -m "fix: recognize question/questionnaire tools as pending questions (issue #26)"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Self-Review
|
|
202
|
+
|
|
203
|
+
**Spec coverage:**
|
|
204
|
+
- Spec 3.1 (QUESTION_TOOL_NAMES replacing both hardcoded checks) → Task 2 steps 3. ✓
|
|
205
|
+
- Spec 3.2 (questionFromArgs pi shapes: `args.question`, `items[].prompt`, fallback `item.question`) → Task 1 step 3. ✓
|
|
206
|
+
- Spec 3.4 tests 1-4 (question start→needs_input, questionnaire extraction, end→working, detached unchanged) → Task 2 step 1 + Task 1 step 1. ✓
|
|
207
|
+
- Spec 3.3 downstream flow unchanged → asserted via `summary`/`needsInput` in Task 2 test 1; no production code touched outside `events.mjs`. ✓
|
|
208
|
+
|
|
209
|
+
**Placeholder scan:** every step carries concrete code or exact commands; no TBD/TODO/"similar to". ✓
|
|
210
|
+
|
|
211
|
+
**Type consistency:** `QUESTION_TOOL_NAMES` used identically in both replace steps; arg fields (`question`, `questions[].prompt`) match the pi extension schemas (`~/.pi/agent/extensions/question.ts`, `questionnaire.ts`). ✓
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Spec: Recognize pi `question`/`questionnaire` tools as pending-question sources
|
|
2
|
+
|
|
3
|
+
- **Issue**: zhuxixi/pi-agent-board#26
|
|
4
|
+
- **Date**: 2026-08-22
|
|
5
|
+
- **Status**: draft — awaiting user confirmation before implementation
|
|
6
|
+
- **Type**: bug fix (event reducer + arg extraction), no data-model change
|
|
7
|
+
|
|
8
|
+
## 1. Problem
|
|
9
|
+
|
|
10
|
+
A pi session blocked on the built-in `question` (or `questionnaire`) tool shows a
|
|
11
|
+
"Question" summary but stays in the **RUNNING** group instead of **NEEDS ANSWER**.
|
|
12
|
+
|
|
13
|
+
Observed 2026-08-22 01:26 on view `view_e809b1a3a2` ("jfox moc密度PR审查与监控"):
|
|
14
|
+
row summary "Question", `semanticState=working`, `pendingQuestions=[]`.
|
|
15
|
+
|
|
16
|
+
## 2. Root cause
|
|
17
|
+
|
|
18
|
+
The reducer only recognizes ONE question-tool name — `ask_questions`
|
|
19
|
+
(`src/core/events.mjs` L83/L97). Pi's actual question tools are named
|
|
20
|
+
`question` and `questionnaire` (user extensions:
|
|
21
|
+
`~/.pi/agent/extensions/question.ts`, `questionnaire.ts`).
|
|
22
|
+
|
|
23
|
+
When `tool_execution_start` arrives with `toolName: "question"`:
|
|
24
|
+
1. Name check fails → `else if (pendingQuestions.length === 0)` branch runs →
|
|
25
|
+
`semanticState = "working"`, `currentTool.summary = toolSummary("question")` =
|
|
26
|
+
`capitalize("question")` = `"Question"` (heuristics.mjs default branch).
|
|
27
|
+
2. `preservePendingQuestion` finds no pending question → no-op.
|
|
28
|
+
3. Result: row grouped by `semanticState` = "working" → RUNNING, summary "Question".
|
|
29
|
+
|
|
30
|
+
Correction to issue body: the "design gap B" claim (pending questions don't affect
|
|
31
|
+
grouping) is **wrong** — `preservePendingQuestion` already sets
|
|
32
|
+
`semanticState = "needs_input"` when `pendingQuestions` is non-empty. The ONLY gap is
|
|
33
|
+
tool-name recognition (plus arg-shape extraction). See research/root-cause.md.
|
|
34
|
+
|
|
35
|
+
## 3. Design
|
|
36
|
+
|
|
37
|
+
All changes in `src/core/events.mjs` + tests. No store/schema/type changes.
|
|
38
|
+
|
|
39
|
+
### 3.1 Question-tool name set
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const QUESTION_TOOL_NAMES = new Set(["ask_questions", "question", "questionnaire"]);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Replace both `name === "ask_questions"` checks (tool_execution_start L83,
|
|
46
|
+
tool_execution_end L97) with `QUESTION_TOOL_NAMES.has(name)` /
|
|
47
|
+
`QUESTION_TOOL_NAMES.has(event.toolName ?? "")`.
|
|
48
|
+
|
|
49
|
+
Rationale: static set matches the existing hardcoded-name precedent; all three names
|
|
50
|
+
are in the family of pi/agent question tools. A configurable list is deferred (4.2).
|
|
51
|
+
|
|
52
|
+
### 3.2 `questionFromArgs` — support pi arg shapes
|
|
53
|
+
|
|
54
|
+
Current: reads `args.questions[].question` (ask_questions shape).
|
|
55
|
+
|
|
56
|
+
Extend to:
|
|
57
|
+
|
|
58
|
+
| Tool | Args shape | Extract from |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `ask_questions` | `{ questions: [{ question }] }` | `item.question` |
|
|
61
|
+
| `question` (pi) | `{ question: string, options: [...] }` | `args.question` |
|
|
62
|
+
| `questionnaire` (pi) | `{ questions: [{ prompt, options }] }` | `item.prompt`, fallback `item.question` |
|
|
63
|
+
|
|
64
|
+
Priority in `questions[]` items: `item.question ?? item.prompt`. Keep the
|
|
65
|
+
"Answer the pending question" fallback.
|
|
66
|
+
|
|
67
|
+
### 3.3 Downstream flow (already correct — verify, don't change)
|
|
68
|
+
|
|
69
|
+
- `tool_execution_start` (interactive, question tool): `upsertPendingQuestion` →
|
|
70
|
+
`preservePendingQuestion` → `semanticState = "needs_input"`, `currentTool = null`,
|
|
71
|
+
`question = first.question`. Row moves to NEEDS ANSWER group; summary shows the
|
|
72
|
+
question text (deriveSummary: needs_input + question).
|
|
73
|
+
- `tool_execution_end` (interactive, question tool): `removePendingQuestion`,
|
|
74
|
+
`semanticState = "working"`, `question = null` — back to RUNNING after the answer.
|
|
75
|
+
- Non-interactive (detached worker) path: `opts.interactive` gate unchanged →
|
|
76
|
+
question tools keep the old generic behavior (no pending-question tracking).
|
|
77
|
+
|
|
78
|
+
### 3.4 Tests (`test/events.test.mjs`)
|
|
79
|
+
|
|
80
|
+
Mirror the existing `ask_questions` coverage (L109-160):
|
|
81
|
+
|
|
82
|
+
1. `tool_execution_start` with `toolName: "question"` (args `{ question: "Approve?" }`),
|
|
83
|
+
interactive → `pendingQuestions = [{ toolCallId, question: "Approve?" }]`,
|
|
84
|
+
`semanticState === "needs_input"`, `currentTool === null`, summary = question text.
|
|
85
|
+
2. Same for `toolName: "questionnaire"` with args `{ questions: [{ prompt: "Pick scope" }] }`
|
|
86
|
+
→ extracted question "Pick scope".
|
|
87
|
+
3. `tool_execution_end` with `toolName: "question"` → pendingQuestions empty,
|
|
88
|
+
`semanticState === "working"`.
|
|
89
|
+
4. Detached (no `interactive`) `question` start → unchanged legacy behavior
|
|
90
|
+
(`currentTool.name === "question"`, `pendingQuestions = []`).
|
|
91
|
+
|
|
92
|
+
## 4. Non-goals / deferred
|
|
93
|
+
|
|
94
|
+
1. No configurable question-tool list (env/config escape hatch). Add later only if a
|
|
95
|
+
harness with a different question-tool name appears. Keeping the set static avoids
|
|
96
|
+
new config surface.
|
|
97
|
+
2. No changes to `finalizeRun`, `projectViewState`, `deriveSummary`, or grouping logic.
|
|
98
|
+
3. Non-interactive path behavior unchanged.
|
|
99
|
+
4. No i18n / label changes.
|
|
100
|
+
|
|
101
|
+
## 5. Edge cases
|
|
102
|
+
|
|
103
|
+
- Two concurrent question tool calls: keyed by `toolCallId`; first pending question wins
|
|
104
|
+
display. Unchanged from `ask_questions` behavior.
|
|
105
|
+
- Non-TUI pi question tool returns an error result immediately → start/end pair clears
|
|
106
|
+
the pending question within the same cycle; transient needs_input is harmless.
|
|
107
|
+
- `agent_start` / `input` events already clear `pendingQuestions` in the foreground
|
|
108
|
+
path (service.mjs L524-535) — no interaction.
|
|
109
|
+
|
|
110
|
+
## 6. Verification
|
|
111
|
+
|
|
112
|
+
- `npm run verify` (project gate) with the new events tests.
|
|
113
|
+
- Manual: start a pi session under the board, have it call the `question` tool →
|
|
114
|
+
row shows the question text in NEEDS ANSWER group; answer it → row returns to RUNNING.
|
package/package.json
CHANGED
package/src/core/events.mjs
CHANGED
|
@@ -17,6 +17,9 @@ import { assistantText, detectNeedsInput, toolPath, toolSummary, truncate } from
|
|
|
17
17
|
|
|
18
18
|
const PREVIEW_MAX = 240;
|
|
19
19
|
|
|
20
|
+
/** Tool names whose interactive execution blocks on a user answer. */
|
|
21
|
+
const QUESTION_TOOL_NAMES = new Set(["ask_questions", "question", "questionnaire"]);
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* Build the initial status for a freshly-launched run.
|
|
22
25
|
* @param {RunConfig} config
|
|
@@ -80,7 +83,7 @@ export function reduceEvent(status, event, now, opts = {}) {
|
|
|
80
83
|
const name = event.toolName ?? event.args?.name ?? "tool";
|
|
81
84
|
const args = event.args ?? {};
|
|
82
85
|
status.toolCount += 1;
|
|
83
|
-
if (opts.interactive && name
|
|
86
|
+
if (opts.interactive && QUESTION_TOOL_NAMES.has(name)) {
|
|
84
87
|
upsertPendingQuestion(status, event.toolCallId, questionFromArgs(args));
|
|
85
88
|
} else if (pendingQuestions(status).length === 0) {
|
|
86
89
|
status.currentTool = { name, path: toolPath(args), summary: toolSummary(name, args) };
|
|
@@ -94,7 +97,7 @@ export function reduceEvent(status, event, now, opts = {}) {
|
|
|
94
97
|
}
|
|
95
98
|
case "tool_execution_end": {
|
|
96
99
|
if (event.isError) status.error = `Tool ${event.toolName ?? ""} failed`.trim();
|
|
97
|
-
if (opts.interactive && event.toolName
|
|
100
|
+
if (opts.interactive && QUESTION_TOOL_NAMES.has(event.toolName ?? "")) removePendingQuestion(status, event.toolCallId);
|
|
98
101
|
status.currentTool = null;
|
|
99
102
|
status.semanticState = "working";
|
|
100
103
|
status.question = null;
|
|
@@ -227,8 +230,9 @@ function pendingQuestions(status) {
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
function questionFromArgs(args) {
|
|
233
|
+
if (typeof args?.question === "string" && args.question.trim()) return args.question.trim();
|
|
230
234
|
for (const item of Array.isArray(args?.questions) ? args.questions : []) {
|
|
231
|
-
const question = String(item?.question ?? "").trim();
|
|
235
|
+
const question = String(item?.question ?? item?.prompt ?? "").trim();
|
|
232
236
|
if (question) return question;
|
|
233
237
|
}
|
|
234
238
|
return "Answer the pending question";
|