@agimon-ai/po-user-feedback 0.0.0-alpha.1 → 0.0.0-alpha.2
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/dist/extensions/native.mjs +576 -0
- package/dist/extensions/native.mjs.map +1 -0
- package/package.json +34 -8
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { defineNativePlugin, defineToolRenderer } from "@agimon-ai/po-core/native";
|
|
2
|
+
import { View } from "react-native-unistyles/components/native/View";
|
|
3
|
+
import { Text } from "react-native-unistyles/components/native/Text";
|
|
4
|
+
import { Button, Markdown, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, SectionLabel, Textarea, fontOf, selectionTick, toolTone } from "@agimon-ai/po-native-components";
|
|
5
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
|
|
8
|
+
import { Pressable } from "react-native-unistyles/components/native/Pressable";
|
|
9
|
+
import { useMemo, useState } from "react";
|
|
10
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/askUserText.ts
|
|
11
|
+
function isRecord$1(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function asString$1(value) {
|
|
15
|
+
return typeof value === "string" ? value : "";
|
|
16
|
+
}
|
|
17
|
+
/** The call header: how many questions, and their headers in parentheses. */
|
|
18
|
+
function askCallSummary(args) {
|
|
19
|
+
const questions = Array.isArray(args.questions) ? args.questions.filter(isRecord$1) : [];
|
|
20
|
+
const headers = questions.map((question) => asString$1(question.header)).filter(Boolean);
|
|
21
|
+
return {
|
|
22
|
+
count: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
|
23
|
+
headers: headers.join(", ")
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The result body the TUI's renderResult chooses: a cancelled glyph, the
|
|
28
|
+
* voice prompt when the questionnaire was handed to voice, the answered
|
|
29
|
+
* list, or the raw text when the tool attached no details (an error path).
|
|
30
|
+
*/
|
|
31
|
+
function askResultView(details, output) {
|
|
32
|
+
if (!isRecord$1(details) || !Array.isArray(details.answers)) return {
|
|
33
|
+
kind: "text",
|
|
34
|
+
text: output
|
|
35
|
+
};
|
|
36
|
+
if (details.cancelled === true) return { kind: "cancelled" };
|
|
37
|
+
if (details.delivery === "voice" && typeof details.voicePrompt === "string") return {
|
|
38
|
+
kind: "voice",
|
|
39
|
+
prompt: details.voicePrompt
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
kind: "answers",
|
|
43
|
+
answers: details.answers.filter(isRecord$1).map((answer) => ({
|
|
44
|
+
question: asString$1(answer.question),
|
|
45
|
+
answer: answer.kind === "multi" ? (Array.isArray(answer.selected) ? answer.selected : []).filter((v) => typeof v === "string").join(", ") : asString$1(answer.answer)
|
|
46
|
+
}))
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/AskUserQuestionToolMessage.native.tsx
|
|
51
|
+
function AskUserQuestionToolMessage(props) {
|
|
52
|
+
const summary = askCallSummary(props.args);
|
|
53
|
+
const view = askResultView(props.result?.details, props.output);
|
|
54
|
+
return /* @__PURE__ */ jsxs(MessageItem, {
|
|
55
|
+
tone: toolTone(props),
|
|
56
|
+
children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
57
|
+
title: "ask_user_question",
|
|
58
|
+
children: `${summary.count}${summary.headers ? ` (${summary.headers})` : ""}`
|
|
59
|
+
}), /* @__PURE__ */ jsx(MessageItemBody, {
|
|
60
|
+
testID: "tool-result-ask_user_question",
|
|
61
|
+
children: props.running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
62
|
+
tone: "running",
|
|
63
|
+
children: "answer below to continue"
|
|
64
|
+
}) : view.kind === "cancelled" ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
65
|
+
tone: "running",
|
|
66
|
+
children: "cancelled"
|
|
67
|
+
}) : view.kind === "answers" ? view.answers.length === 0 ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
68
|
+
tone: "ok",
|
|
69
|
+
children: "submitted"
|
|
70
|
+
}) : /* @__PURE__ */ jsx(View, {
|
|
71
|
+
style: styles$1.answers,
|
|
72
|
+
children: view.answers.map((answer, index) => /* @__PURE__ */ jsxs(Text, {
|
|
73
|
+
style: styles$1.text,
|
|
74
|
+
children: [
|
|
75
|
+
/* @__PURE__ */ jsx(Text, {
|
|
76
|
+
style: styles$1.check,
|
|
77
|
+
children: "✓ "
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ jsx(Text, {
|
|
80
|
+
style: styles$1.question,
|
|
81
|
+
children: answer.question
|
|
82
|
+
}),
|
|
83
|
+
": ",
|
|
84
|
+
answer.answer
|
|
85
|
+
]
|
|
86
|
+
}, `${String(index)}:${answer.question}`))
|
|
87
|
+
}) : view.kind === "voice" ? /* @__PURE__ */ jsx(Text, {
|
|
88
|
+
style: styles$1.text,
|
|
89
|
+
children: view.prompt
|
|
90
|
+
}) : view.text ? /* @__PURE__ */ jsx(Text, {
|
|
91
|
+
style: [styles$1.text, props.isError && styles$1.error],
|
|
92
|
+
accessibilityRole: props.isError ? "alert" : void 0,
|
|
93
|
+
children: view.text
|
|
94
|
+
}) : /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
95
|
+
tone: props.isError ? "error" : "ok",
|
|
96
|
+
children: props.isError ? "failed" : "submitted"
|
|
97
|
+
})
|
|
98
|
+
})]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const styles$1 = StyleSheet.create((theme) => ({
|
|
102
|
+
answers: {
|
|
103
|
+
gap: theme.spacing(2),
|
|
104
|
+
uni__dependencies: [0]
|
|
105
|
+
},
|
|
106
|
+
text: {
|
|
107
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
108
|
+
fontSize: theme.fontSize.base,
|
|
109
|
+
lineHeight: theme.lineHeightRelaxed.base,
|
|
110
|
+
color: theme.colors.dim,
|
|
111
|
+
uni__dependencies: [0]
|
|
112
|
+
},
|
|
113
|
+
check: {
|
|
114
|
+
color: theme.colors.green,
|
|
115
|
+
uni__dependencies: [0]
|
|
116
|
+
},
|
|
117
|
+
question: {
|
|
118
|
+
color: theme.colors.cyan,
|
|
119
|
+
uni__dependencies: [0]
|
|
120
|
+
},
|
|
121
|
+
error: {
|
|
122
|
+
color: theme.colors.red,
|
|
123
|
+
uni__dependencies: [0]
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/types/askUserWire.ts
|
|
128
|
+
/**
|
|
129
|
+
* How a rich client answers the whole questionnaire in one reply.
|
|
130
|
+
*
|
|
131
|
+
* Pi's select request carries a title and a list of labels and nothing else,
|
|
132
|
+
* so over RPC the questionnaire degrades into one request per question, asked
|
|
133
|
+
* forwards only. A client that can already see the whole call (the cockpit
|
|
134
|
+
* reads it from the tool's arguments) has no use for that: it renders every
|
|
135
|
+
* question at once, lets the reader move between them, and replies once.
|
|
136
|
+
*
|
|
137
|
+
* The reply rides on the select response, because that is the only channel a
|
|
138
|
+
* blocked extension has. An ordinary label is still an ordinary label, so a
|
|
139
|
+
* client that knows nothing about this keeps the per-question flow exactly as
|
|
140
|
+
* it was; only a response carrying the prefix is read as the whole set.
|
|
141
|
+
*
|
|
142
|
+
* It lives under src/types because that is the one server root a browser
|
|
143
|
+
* bundle may read, so neither half can drift from the other.
|
|
144
|
+
*/
|
|
145
|
+
const ANSWER_ENVELOPE_PREFIX = "doom-ask-user/v1:";
|
|
146
|
+
function encodeAnswerEnvelope(answers) {
|
|
147
|
+
return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/questionnaireDraft.ts
|
|
151
|
+
/** The row every question gets, so a reader always has a way past a bad set of options. */
|
|
152
|
+
const CUSTOM_LABEL = "Type something.";
|
|
153
|
+
const EMPTY_ENTRY = {
|
|
154
|
+
selected: [],
|
|
155
|
+
custom: null,
|
|
156
|
+
notes: ""
|
|
157
|
+
};
|
|
158
|
+
function isRecord(value) {
|
|
159
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
160
|
+
}
|
|
161
|
+
function asString(value) {
|
|
162
|
+
return typeof value === "string" ? value : "";
|
|
163
|
+
}
|
|
164
|
+
function readOption(value) {
|
|
165
|
+
if (!isRecord(value)) return null;
|
|
166
|
+
const label = asString(value.label);
|
|
167
|
+
if (label === "") return null;
|
|
168
|
+
const preview = asString(value.preview);
|
|
169
|
+
return {
|
|
170
|
+
label,
|
|
171
|
+
description: asString(value.description),
|
|
172
|
+
...preview === "" ? {} : { preview }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** The questions as the tool was called with them; an unreadable call yields none. */
|
|
176
|
+
function readPromptQuestions(args) {
|
|
177
|
+
if (!Array.isArray(args.questions)) return [];
|
|
178
|
+
return args.questions.flatMap((entry) => {
|
|
179
|
+
if (!isRecord(entry)) return [];
|
|
180
|
+
const question = asString(entry.question);
|
|
181
|
+
if (question === "") return [];
|
|
182
|
+
const options = (Array.isArray(entry.options) ? entry.options : []).flatMap((option) => {
|
|
183
|
+
const read = readOption(option);
|
|
184
|
+
return read === null ? [] : [read];
|
|
185
|
+
});
|
|
186
|
+
return [{
|
|
187
|
+
question,
|
|
188
|
+
header: asString(entry.header),
|
|
189
|
+
multiSelect: entry.multiSelect === true,
|
|
190
|
+
options
|
|
191
|
+
}];
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function emptyDraft(count) {
|
|
195
|
+
return Array.from({ length: count }, () => EMPTY_ENTRY);
|
|
196
|
+
}
|
|
197
|
+
function draftEntry(draft, index) {
|
|
198
|
+
return draft[index] ?? EMPTY_ENTRY;
|
|
199
|
+
}
|
|
200
|
+
function withEntry(draft, index, patch) {
|
|
201
|
+
if (index < 0 || index >= draft.length) return draft;
|
|
202
|
+
return draft.map((entry, position) => position === index ? {
|
|
203
|
+
...entry,
|
|
204
|
+
...patch
|
|
205
|
+
} : entry);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Picks an option. A single-select question replaces its choice; a multiSelect
|
|
209
|
+
* one toggles. Either way the typed answer is dropped, because an option and
|
|
210
|
+
* free text are two answers to the same question and only one can be sent.
|
|
211
|
+
*/
|
|
212
|
+
function chooseOption(draft, index, label, multiSelect) {
|
|
213
|
+
const current = draftEntry(draft, index).selected;
|
|
214
|
+
return withEntry(draft, index, {
|
|
215
|
+
selected: multiSelect ? current.includes(label) ? current.filter((entry) => entry !== label) : [...current, label] : [label],
|
|
216
|
+
custom: null
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/** Types an answer instead of choosing one; null returns to the options. */
|
|
220
|
+
function setCustom(draft, index, custom) {
|
|
221
|
+
return withEntry(draft, index, {
|
|
222
|
+
custom,
|
|
223
|
+
selected: []
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function setNotes(draft, index, notes) {
|
|
227
|
+
return withEntry(draft, index, { notes });
|
|
228
|
+
}
|
|
229
|
+
function isAnswered(entry) {
|
|
230
|
+
return entry.selected.length > 0 || entry.custom !== null && entry.custom.trim() !== "";
|
|
231
|
+
}
|
|
232
|
+
/** The next question still needing an answer, searching after `from` first; -1 when none is left. */
|
|
233
|
+
function nextUnanswered(draft, from) {
|
|
234
|
+
const after = draft.findIndex((entry, index) => index > from && !isAnswered(entry));
|
|
235
|
+
return after >= 0 ? after : draft.findIndex((entry) => !isAnswered(entry));
|
|
236
|
+
}
|
|
237
|
+
function isComplete(draft) {
|
|
238
|
+
return draft.length > 0 && draft.every(isAnswered);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The answers as the tool reads them back. `preview` is left off: the session
|
|
242
|
+
* half looks it up from the option it declared, so sending one here would only
|
|
243
|
+
* be a claim it has to ignore.
|
|
244
|
+
*/
|
|
245
|
+
function draftAnswers(questions, draft) {
|
|
246
|
+
return questions.flatMap((question, index) => {
|
|
247
|
+
const entry = draftEntry(draft, index);
|
|
248
|
+
if (!isAnswered(entry)) return [];
|
|
249
|
+
const notes = entry.notes.trim();
|
|
250
|
+
const base = {
|
|
251
|
+
questionIndex: index,
|
|
252
|
+
question: question.question,
|
|
253
|
+
...notes === "" ? {} : { notes }
|
|
254
|
+
};
|
|
255
|
+
if (entry.custom !== null && entry.custom.trim() !== "") return [{
|
|
256
|
+
...base,
|
|
257
|
+
kind: "custom",
|
|
258
|
+
answer: entry.custom
|
|
259
|
+
}];
|
|
260
|
+
if (question.multiSelect) return [{
|
|
261
|
+
...base,
|
|
262
|
+
kind: "multi",
|
|
263
|
+
answer: null,
|
|
264
|
+
selected: [...entry.selected]
|
|
265
|
+
}];
|
|
266
|
+
return [{
|
|
267
|
+
...base,
|
|
268
|
+
kind: "option",
|
|
269
|
+
answer: entry.selected[0] ?? null
|
|
270
|
+
}];
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/QuestionnairePrompt.native.tsx
|
|
275
|
+
/** Pure presentation for the approved-target stories and the native prompt host. */
|
|
276
|
+
function QuestionnaireView(props) {
|
|
277
|
+
const { questions, draft, current, previewIndex, onDraft, onCurrent, onPreview, onSubmit, onCancel } = props;
|
|
278
|
+
const question = questions[current];
|
|
279
|
+
const entry = draftEntry(draft, current);
|
|
280
|
+
const preview = question?.options[previewIndex]?.preview;
|
|
281
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
282
|
+
style: styles.frame,
|
|
283
|
+
testID: "questionnaire",
|
|
284
|
+
children: [
|
|
285
|
+
/* @__PURE__ */ jsx(ScrollView, {
|
|
286
|
+
horizontal: true,
|
|
287
|
+
contentContainerStyle: styles.steps,
|
|
288
|
+
accessibilityLabel: "Questions",
|
|
289
|
+
children: questions.map((step, index) => /* @__PURE__ */ jsx(Button, {
|
|
290
|
+
size: "card",
|
|
291
|
+
variant: index === current ? "secondary" : "ghost",
|
|
292
|
+
"aria-selected": index === current,
|
|
293
|
+
accessibilityLabel: `${step.header || `Question ${String(index + 1)}`}, ${isAnswered(draftEntry(draft, index)) ? "answered" : "not answered"}`,
|
|
294
|
+
testID: `questionnaire-step-${String(index)}`,
|
|
295
|
+
onPress: () => {
|
|
296
|
+
selectionTick();
|
|
297
|
+
onCurrent(index);
|
|
298
|
+
},
|
|
299
|
+
children: `${isAnswered(draftEntry(draft, index)) ? "✓ " : ""}${step.header || `Question ${String(index + 1)}`}`
|
|
300
|
+
}, `${String(index)}:${step.question}`))
|
|
301
|
+
}),
|
|
302
|
+
/* @__PURE__ */ jsx(ScrollView, {
|
|
303
|
+
style: styles.body,
|
|
304
|
+
contentContainerStyle: styles.content,
|
|
305
|
+
keyboardShouldPersistTaps: "handled",
|
|
306
|
+
children: question ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
307
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
308
|
+
style: styles.meta,
|
|
309
|
+
children: [
|
|
310
|
+
"Question ",
|
|
311
|
+
current + 1,
|
|
312
|
+
" / ",
|
|
313
|
+
questions.length,
|
|
314
|
+
question.multiSelect ? " · Choose any that fit" : ""
|
|
315
|
+
]
|
|
316
|
+
}),
|
|
317
|
+
/* @__PURE__ */ jsx(Text, {
|
|
318
|
+
style: styles.question,
|
|
319
|
+
testID: "questionnaire-question",
|
|
320
|
+
children: question.question
|
|
321
|
+
}),
|
|
322
|
+
question.options.map((option, index) => {
|
|
323
|
+
const chosen = entry.custom === null && entry.selected.includes(option.label);
|
|
324
|
+
return /* @__PURE__ */ jsxs(Pressable, {
|
|
325
|
+
accessibilityRole: question.multiSelect ? "checkbox" : "radio",
|
|
326
|
+
"aria-checked": chosen,
|
|
327
|
+
accessibilityLabel: option.label,
|
|
328
|
+
accessibilityHint: option.description,
|
|
329
|
+
testID: `questionnaire-option-${String(index)}`,
|
|
330
|
+
style: [styles.option, chosen && styles.chosen],
|
|
331
|
+
onFocus: () => onPreview(index),
|
|
332
|
+
onPress: () => {
|
|
333
|
+
selectionTick();
|
|
334
|
+
onPreview(index);
|
|
335
|
+
onDraft(chooseOption(draft, current, option.label, question.multiSelect), !question.multiSelect);
|
|
336
|
+
},
|
|
337
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
338
|
+
style: [styles.mark, chosen && styles.selectedInk],
|
|
339
|
+
children: question.multiSelect ? chosen ? "☑" : "☐" : chosen ? "●" : "○"
|
|
340
|
+
}), /* @__PURE__ */ jsxs(View, {
|
|
341
|
+
style: styles.optionText,
|
|
342
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
343
|
+
style: styles.label,
|
|
344
|
+
children: option.label
|
|
345
|
+
}), option.description ? /* @__PURE__ */ jsx(Text, {
|
|
346
|
+
style: styles.meta,
|
|
347
|
+
children: option.description
|
|
348
|
+
}) : null]
|
|
349
|
+
})]
|
|
350
|
+
}, `${String(index)}:${option.label}`);
|
|
351
|
+
}),
|
|
352
|
+
/* @__PURE__ */ jsx(Button, {
|
|
353
|
+
variant: entry.custom === null ? "ghost" : "secondary",
|
|
354
|
+
size: "card",
|
|
355
|
+
testID: "questionnaire-option-custom",
|
|
356
|
+
onPress: () => onDraft(setCustom(draft, current, entry.custom ?? ""), false),
|
|
357
|
+
children: CUSTOM_LABEL
|
|
358
|
+
}),
|
|
359
|
+
entry.custom !== null ? /* @__PURE__ */ jsxs(View, {
|
|
360
|
+
style: styles.field,
|
|
361
|
+
children: [/* @__PURE__ */ jsx(SectionLabel, { children: "Your answer" }), /* @__PURE__ */ jsx(Textarea, {
|
|
362
|
+
autoFocus: true,
|
|
363
|
+
rows: 2,
|
|
364
|
+
accessibilityLabel: "Your answer",
|
|
365
|
+
testID: "questionnaire-custom",
|
|
366
|
+
value: entry.custom,
|
|
367
|
+
placeholder: "type your answer…",
|
|
368
|
+
onChangeText: (value) => onDraft(setCustom(draft, current, value), false)
|
|
369
|
+
})]
|
|
370
|
+
}) : null,
|
|
371
|
+
preview && entry.custom === null ? /* @__PURE__ */ jsxs(View, {
|
|
372
|
+
style: styles.preview,
|
|
373
|
+
testID: "questionnaire-preview",
|
|
374
|
+
children: [/* @__PURE__ */ jsx(SectionLabel, { children: "Preview" }), /* @__PURE__ */ jsx(Markdown, { text: preview })]
|
|
375
|
+
}) : null,
|
|
376
|
+
/* @__PURE__ */ jsxs(View, {
|
|
377
|
+
style: styles.field,
|
|
378
|
+
children: [/* @__PURE__ */ jsx(SectionLabel, { children: "Note (optional)" }), /* @__PURE__ */ jsx(Textarea, {
|
|
379
|
+
rows: 1,
|
|
380
|
+
accessibilityLabel: "Note (optional)",
|
|
381
|
+
testID: "questionnaire-notes",
|
|
382
|
+
value: entry.notes,
|
|
383
|
+
placeholder: "anything the options do not cover",
|
|
384
|
+
onChangeText: (value) => onDraft(setNotes(draft, current, value), false)
|
|
385
|
+
})]
|
|
386
|
+
})
|
|
387
|
+
] }) : /* @__PURE__ */ jsx(Text, {
|
|
388
|
+
style: styles.meta,
|
|
389
|
+
children: "the agent asked something this cockpit could not read"
|
|
390
|
+
})
|
|
391
|
+
}),
|
|
392
|
+
/* @__PURE__ */ jsxs(View, {
|
|
393
|
+
style: styles.actions,
|
|
394
|
+
children: [
|
|
395
|
+
questions.length > 1 && !isComplete(draft) ? /* @__PURE__ */ jsx(Button, {
|
|
396
|
+
size: "card",
|
|
397
|
+
disabled: !isAnswered(entry),
|
|
398
|
+
onPress: () => onDraft(draft, true),
|
|
399
|
+
testID: "questionnaire-next",
|
|
400
|
+
children: "Next question"
|
|
401
|
+
}) : null,
|
|
402
|
+
/* @__PURE__ */ jsx(Button, {
|
|
403
|
+
size: "card",
|
|
404
|
+
onPress: onCancel,
|
|
405
|
+
testID: "questionnaire-cancel",
|
|
406
|
+
children: "cancel"
|
|
407
|
+
}),
|
|
408
|
+
/* @__PURE__ */ jsx(Button, {
|
|
409
|
+
size: "card",
|
|
410
|
+
variant: "primary",
|
|
411
|
+
disabled: !isComplete(draft),
|
|
412
|
+
onPress: onSubmit,
|
|
413
|
+
testID: "questionnaire-submit",
|
|
414
|
+
children: "submit answers"
|
|
415
|
+
})
|
|
416
|
+
]
|
|
417
|
+
})
|
|
418
|
+
]
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function Questionnaire({ questions, answer, cancel }) {
|
|
422
|
+
const [draft, setDraft] = useState(() => emptyDraft(questions.length));
|
|
423
|
+
const [current, setCurrent] = useState(0);
|
|
424
|
+
const [previewIndex, setPreviewIndex] = useState(0);
|
|
425
|
+
const show = (index) => {
|
|
426
|
+
setCurrent(index);
|
|
427
|
+
setPreviewIndex(0);
|
|
428
|
+
};
|
|
429
|
+
return /* @__PURE__ */ jsx(QuestionnaireView, {
|
|
430
|
+
questions,
|
|
431
|
+
draft,
|
|
432
|
+
current,
|
|
433
|
+
previewIndex,
|
|
434
|
+
onDraft: (next, settles) => {
|
|
435
|
+
setDraft(next);
|
|
436
|
+
if (!settles) return;
|
|
437
|
+
const pending = nextUnanswered(next, current);
|
|
438
|
+
if (pending >= 0 && pending !== current) show(pending);
|
|
439
|
+
},
|
|
440
|
+
onCurrent: show,
|
|
441
|
+
onPreview: setPreviewIndex,
|
|
442
|
+
onSubmit: () => {
|
|
443
|
+
if (isComplete(draft)) answer(encodeAnswerEnvelope(draftAnswers(questions, draft)));
|
|
444
|
+
},
|
|
445
|
+
onCancel: cancel
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** The dialog identity owns the draft, so another session or request cannot inherit its answers. */
|
|
449
|
+
function QuestionnairePrompt({ args, sessionId, dialog, answer, cancel }) {
|
|
450
|
+
const questions = useMemo(() => readPromptQuestions(args), [args]);
|
|
451
|
+
return /* @__PURE__ */ jsx(Questionnaire, {
|
|
452
|
+
questions,
|
|
453
|
+
answer,
|
|
454
|
+
cancel
|
|
455
|
+
}, `${sessionId}:${dialog.id}:${String(questions.length)}`);
|
|
456
|
+
}
|
|
457
|
+
const styles = StyleSheet.create((theme) => ({
|
|
458
|
+
frame: {
|
|
459
|
+
maxHeight: "75%",
|
|
460
|
+
flexShrink: 1,
|
|
461
|
+
backgroundColor: theme.colors.rail,
|
|
462
|
+
borderTopWidth: 1,
|
|
463
|
+
borderColor: theme.colors["edge-magenta"],
|
|
464
|
+
uni__dependencies: [0]
|
|
465
|
+
},
|
|
466
|
+
steps: {
|
|
467
|
+
gap: theme.spacing(2),
|
|
468
|
+
padding: theme.spacing(3),
|
|
469
|
+
uni__dependencies: [0]
|
|
470
|
+
},
|
|
471
|
+
body: { flexShrink: 1 },
|
|
472
|
+
content: {
|
|
473
|
+
gap: theme.spacing(2),
|
|
474
|
+
paddingHorizontal: theme.spacing(3),
|
|
475
|
+
paddingBottom: theme.spacing(3),
|
|
476
|
+
uni__dependencies: [0]
|
|
477
|
+
},
|
|
478
|
+
question: {
|
|
479
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
480
|
+
fontSize: theme.fontSize.base,
|
|
481
|
+
lineHeight: theme.lineHeightRelaxed.base,
|
|
482
|
+
color: theme.colors.hi,
|
|
483
|
+
uni__dependencies: [0]
|
|
484
|
+
},
|
|
485
|
+
meta: {
|
|
486
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
487
|
+
fontSize: theme.fontSize.sm,
|
|
488
|
+
lineHeight: theme.lineHeightRelaxed.sm,
|
|
489
|
+
color: theme.colors.dim,
|
|
490
|
+
uni__dependencies: [0]
|
|
491
|
+
},
|
|
492
|
+
option: {
|
|
493
|
+
flexDirection: "row",
|
|
494
|
+
gap: theme.spacing(3),
|
|
495
|
+
minHeight: 44,
|
|
496
|
+
padding: theme.spacing(3),
|
|
497
|
+
borderWidth: 1,
|
|
498
|
+
borderColor: theme.colors["border-soft"],
|
|
499
|
+
borderRadius: theme.borderRadius.md,
|
|
500
|
+
uni__dependencies: [0]
|
|
501
|
+
},
|
|
502
|
+
chosen: {
|
|
503
|
+
borderColor: theme.colors["edge-magenta"],
|
|
504
|
+
backgroundColor: theme.colors["tint-magenta"],
|
|
505
|
+
uni__dependencies: [0]
|
|
506
|
+
},
|
|
507
|
+
mark: {
|
|
508
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
509
|
+
fontSize: theme.fontSize.lg,
|
|
510
|
+
color: theme.colors.dim,
|
|
511
|
+
uni__dependencies: [0]
|
|
512
|
+
},
|
|
513
|
+
selectedInk: {
|
|
514
|
+
color: theme.colors.magenta,
|
|
515
|
+
uni__dependencies: [0]
|
|
516
|
+
},
|
|
517
|
+
optionText: {
|
|
518
|
+
flex: 1,
|
|
519
|
+
gap: theme.spacing(1),
|
|
520
|
+
uni__dependencies: [0]
|
|
521
|
+
},
|
|
522
|
+
label: {
|
|
523
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
524
|
+
fontSize: theme.fontSize.base,
|
|
525
|
+
lineHeight: theme.lineHeightRelaxed.base,
|
|
526
|
+
fontWeight: theme.fontWeight.semibold,
|
|
527
|
+
color: theme.colors.text,
|
|
528
|
+
uni__dependencies: [0]
|
|
529
|
+
},
|
|
530
|
+
field: {
|
|
531
|
+
gap: theme.spacing(2),
|
|
532
|
+
uni__dependencies: [0]
|
|
533
|
+
},
|
|
534
|
+
preview: {
|
|
535
|
+
gap: theme.spacing(2),
|
|
536
|
+
paddingLeft: theme.spacing(3),
|
|
537
|
+
borderLeftWidth: 2,
|
|
538
|
+
borderColor: theme.colors["edge-magenta"],
|
|
539
|
+
uni__dependencies: [0]
|
|
540
|
+
},
|
|
541
|
+
actions: {
|
|
542
|
+
flexDirection: "row",
|
|
543
|
+
flexWrap: "wrap",
|
|
544
|
+
justifyContent: "flex-end",
|
|
545
|
+
gap: theme.spacing(2),
|
|
546
|
+
padding: theme.spacing(3),
|
|
547
|
+
borderTopWidth: 1,
|
|
548
|
+
borderColor: theme.colors["border-soft"],
|
|
549
|
+
uni__dependencies: [0]
|
|
550
|
+
}
|
|
551
|
+
}));
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.native.tsx
|
|
554
|
+
var askUserQuestion_native_default = defineToolRenderer({
|
|
555
|
+
message: AskUserQuestionToolMessage,
|
|
556
|
+
prompt: {
|
|
557
|
+
claims: (dialog, args) => dialog.method === "select" && readPromptQuestions(args).some((question) => dialog.title.startsWith(question.question)),
|
|
558
|
+
component: QuestionnairePrompt
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region generated/native.ts
|
|
563
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
564
|
+
...identity,
|
|
565
|
+
...value
|
|
566
|
+
};
|
|
567
|
+
const nativePlugin = defineNativePlugin({
|
|
568
|
+
id: "ask-user",
|
|
569
|
+
session: { get toolRenderers() {
|
|
570
|
+
return [via({ tools: ["ask_user_question"] }, askUserQuestion_native_default)];
|
|
571
|
+
} }
|
|
572
|
+
});
|
|
573
|
+
//#endregion
|
|
574
|
+
export { nativePlugin };
|
|
575
|
+
|
|
576
|
+
//# sourceMappingURL=native.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.mjs","names":["isRecord","asString","ToolMessageRenderProps","fontOf","MessageItem","MessageItemBody","MessageItemHeader","MessageItemStatus","toolTone","StyleSheet","askCallSummary","askResultView","AskUserQuestionToolMessage","props","summary","args","view","result","details","output","count","headers","running","kind","answers","length","styles","map","answer","index","String","question","text","check","prompt","isError","error","undefined","create","theme","gap","spacing","uni__dependencies","fontFamily","sans","fontSize","base","lineHeight","lineHeightRelaxed","color","colors","dim","green","cyan","red","styles","ToolPromptRenderProps","Button","fontOf","Markdown","SectionLabel","selectionTick","Textarea","useMemo","useState","StyleSheet","encodeAnswerEnvelope","chooseOption","CUSTOM_LABEL","draftAnswers","draftEntry","emptyDraft","isAnswered","isComplete","nextUnanswered","PromptQuestion","QuestionnaireDraft","readPromptQuestions","setCustom","setNotes","QuestionnaireViewProps","questions","draft","current","previewIndex","onDraft","settles","onCurrent","index","onPreview","onSubmit","onCancel","QuestionnaireView","props","question","entry","preview","options","styles","frame","steps","map","step","String","header","body","content","meta","length","multiSelect","option","chosen","custom","selected","includes","label","description","mark","selectedInk","optionText","field","value","notes","actions","Questionnaire","answer","cancel","setDraft","setCurrent","setPreviewIndex","show","next","pending","QuestionnairePrompt","args","sessionId","dialog","id","create","theme","maxHeight","flexShrink","backgroundColor","colors","rail","borderTopWidth","borderColor","uni__dependencies","gap","spacing","padding","paddingHorizontal","paddingBottom","fontFamily","sans","fontSize","base","lineHeight","lineHeightRelaxed","color","hi","sm","dim","flexDirection","minHeight","borderWidth","borderRadius","md","lg","magenta","flex","fontWeight","semibold","text","paddingLeft","borderLeftWidth","flexWrap","justifyContent","toolAskUserQuestion"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/askUserText.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/AskUserQuestionToolMessage.native.tsx","../../src/types/askUserWire.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/questionnaireDraft.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/QuestionnairePrompt.native.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.native.tsx","../../generated/native.ts"],"sourcesContent":["/**\n * Pure text shaping for the ask_user_question card, apart from React so the\n * unit suite covers it without a DOM. Args and details are wire JSON; the\n * shapes narrowed here mirror src/types/questionnaire.ts, which this browser\n * module may not import at runtime.\n */\n\ntype Args = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nexport interface AskCallSummary {\n /** \"N question(s)\", the TUI heading's purpose. */\n count: string;\n /** The question headers, comma separated; empty when none carry one. */\n headers: string;\n}\n\n/** The call header: how many questions, and their headers in parentheses. */\nexport function askCallSummary(args: Args): AskCallSummary {\n const questions = Array.isArray(args.questions) ? args.questions.filter(isRecord) : [];\n const headers = questions.map((question) => asString(question.header)).filter(Boolean);\n return {\n count: `${questions.length} question${questions.length === 1 ? '' : 's'}`,\n headers: headers.join(', '),\n };\n}\n\nexport interface AnsweredQuestion {\n question: string;\n answer: string;\n}\n\nexport type AskResultView =\n | { kind: 'cancelled' }\n | { kind: 'voice'; prompt: string }\n | { kind: 'answers'; answers: AnsweredQuestion[] }\n | { kind: 'text'; text: string };\n\n/**\n * The result body the TUI's renderResult chooses: a cancelled glyph, the\n * voice prompt when the questionnaire was handed to voice, the answered\n * list, or the raw text when the tool attached no details (an error path).\n */\nexport function askResultView(details: unknown, output: string): AskResultView {\n if (!isRecord(details) || !Array.isArray(details.answers)) return { kind: 'text', text: output };\n if (details.cancelled === true) return { kind: 'cancelled' };\n if (details.delivery === 'voice' && typeof details.voicePrompt === 'string') {\n return { kind: 'voice', prompt: details.voicePrompt };\n }\n const answers = details.answers.filter(isRecord).map((answer) => ({\n question: asString(answer.question),\n answer:\n answer.kind === 'multi'\n ? (Array.isArray(answer.selected) ? answer.selected : []).filter((v) => typeof v === 'string').join(', ')\n : asString(answer.answer),\n }));\n return { kind: 'answers', answers };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/po-core/native';\nimport {\n fontOf,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n toolTone,\n} from '@agimon-ai/po-native-components';\nimport { Text, View } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { askCallSummary, askResultView } from '../_lib/askUserText';\n\nexport function AskUserQuestionToolMessage(props: ToolMessageRenderProps) {\n const summary = askCallSummary(props.args);\n const view = askResultView(props.result?.details, props.output);\n return (\n <MessageItem tone={toolTone(props)}>\n <MessageItemHeader title=\"ask_user_question\">{`${summary.count}${summary.headers ? ` (${summary.headers})` : ''}`}</MessageItemHeader>\n <MessageItemBody testID=\"tool-result-ask_user_question\">\n {props.running ? (\n <MessageItemStatus tone=\"running\">answer below to continue</MessageItemStatus>\n ) : view.kind === 'cancelled' ? (\n <MessageItemStatus tone=\"running\">cancelled</MessageItemStatus>\n ) : view.kind === 'answers' ? (\n view.answers.length === 0 ? (\n <MessageItemStatus tone=\"ok\">submitted</MessageItemStatus>\n ) : (\n <View style={styles.answers}>\n {view.answers.map((answer, index) => (\n <Text key={`${String(index)}:${answer.question}`} style={styles.text}>\n <Text style={styles.check}>✓ </Text>\n <Text style={styles.question}>{answer.question}</Text>: {answer.answer}\n </Text>\n ))}\n </View>\n )\n ) : view.kind === 'voice' ? (\n <Text style={styles.text}>{view.prompt}</Text>\n ) : view.text ? (\n <Text\n style={[styles.text, props.isError && styles.error]}\n accessibilityRole={props.isError ? 'alert' : undefined}\n >\n {view.text}\n </Text>\n ) : (\n <MessageItemStatus tone={props.isError ? 'error' : 'ok'}>\n {props.isError ? 'failed' : 'submitted'}\n </MessageItemStatus>\n )}\n </MessageItemBody>\n </MessageItem>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n answers: { gap: theme.spacing(2) },\n text: {\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.base,\n lineHeight: theme.lineHeightRelaxed.base,\n color: theme.colors.dim,\n },\n check: { color: theme.colors.green },\n question: { color: theme.colors.cyan },\n error: { color: theme.colors.red },\n}));\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'doom-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","/**\n * The questionnaire the cockpit is filling in, as a value.\n *\n * Apart from React so the unit suite covers it without a DOM, the same way\n * askUserText.ts is. The questions come from the running tool call's\n * arguments, which are wire JSON: the shapes narrowed here mirror\n * src/schemas/questionnaire.ts, which a browser module may not import.\n *\n * Everything is held here until the reader submits, which is the whole point.\n * A step the reader goes back to still has what they chose, and the agent is\n * asked once rather than once per question.\n */\nimport type { QuestionAnswer } from '../../../../../../types/questionnaire';\n\n/** The row every question gets, so a reader always has a way past a bad set of options. */\nexport const CUSTOM_LABEL = 'Type something.';\n\nexport interface PromptOption {\n label: string;\n description: string;\n preview?: string;\n}\n\nexport interface PromptQuestion {\n question: string;\n header: string;\n multiSelect: boolean;\n options: PromptOption[];\n}\n\nexport interface QuestionDraft {\n /** Chosen option labels: at most one unless the question is multiSelect. */\n selected: string[];\n /** Text typed in place of an option; null while the reader has not typed any. */\n custom: string | null;\n notes: string;\n}\n\nexport type QuestionnaireDraft = readonly QuestionDraft[];\n\nconst EMPTY_ENTRY: QuestionDraft = { selected: [], custom: null, notes: '' };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readOption(value: unknown): PromptOption | null {\n if (!isRecord(value)) return null;\n const label = asString(value.label);\n if (label === '') return null;\n const preview = asString(value.preview);\n return { label, description: asString(value.description), ...(preview === '' ? {} : { preview }) };\n}\n\n/** The questions as the tool was called with them; an unreadable call yields none. */\nexport function readPromptQuestions(args: Readonly<Record<string, unknown>>): PromptQuestion[] {\n if (!Array.isArray(args.questions)) return [];\n return args.questions.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const question = asString(entry.question);\n if (question === '') return [];\n const options = (Array.isArray(entry.options) ? entry.options : []).flatMap((option) => {\n const read = readOption(option);\n return read === null ? [] : [read];\n });\n return [{ question, header: asString(entry.header), multiSelect: entry.multiSelect === true, options }];\n });\n}\n\nexport function emptyDraft(count: number): QuestionnaireDraft {\n return Array.from({ length: count }, () => EMPTY_ENTRY);\n}\n\nexport function draftEntry(draft: QuestionnaireDraft, index: number): QuestionDraft {\n return draft[index] ?? EMPTY_ENTRY;\n}\n\nfunction withEntry(draft: QuestionnaireDraft, index: number, patch: Partial<QuestionDraft>): QuestionnaireDraft {\n if (index < 0 || index >= draft.length) return draft;\n return draft.map((entry, position) => (position === index ? { ...entry, ...patch } : entry));\n}\n\n/**\n * Picks an option. A single-select question replaces its choice; a multiSelect\n * one toggles. Either way the typed answer is dropped, because an option and\n * free text are two answers to the same question and only one can be sent.\n */\nexport function chooseOption(\n draft: QuestionnaireDraft,\n index: number,\n label: string,\n multiSelect: boolean,\n): QuestionnaireDraft {\n const current = draftEntry(draft, index).selected;\n const selected = multiSelect\n ? current.includes(label)\n ? current.filter((entry) => entry !== label)\n : [...current, label]\n : [label];\n return withEntry(draft, index, { selected, custom: null });\n}\n\n/** Types an answer instead of choosing one; null returns to the options. */\nexport function setCustom(draft: QuestionnaireDraft, index: number, custom: string | null): QuestionnaireDraft {\n return withEntry(draft, index, { custom, selected: [] });\n}\n\nexport function setNotes(draft: QuestionnaireDraft, index: number, notes: string): QuestionnaireDraft {\n return withEntry(draft, index, { notes });\n}\n\nexport function isAnswered(entry: QuestionDraft): boolean {\n return entry.selected.length > 0 || (entry.custom !== null && entry.custom.trim() !== '');\n}\n\n/** The next question still needing an answer, searching after `from` first; -1 when none is left. */\nexport function nextUnanswered(draft: QuestionnaireDraft, from: number): number {\n const after = draft.findIndex((entry, index) => index > from && !isAnswered(entry));\n return after >= 0 ? after : draft.findIndex((entry) => !isAnswered(entry));\n}\n\nexport function isComplete(draft: QuestionnaireDraft): boolean {\n return draft.length > 0 && draft.every(isAnswered);\n}\n\n/**\n * The answers as the tool reads them back. `preview` is left off: the session\n * half looks it up from the option it declared, so sending one here would only\n * be a claim it has to ignore.\n */\nexport function draftAnswers(questions: readonly PromptQuestion[], draft: QuestionnaireDraft): QuestionAnswer[] {\n return questions.flatMap((question, index): QuestionAnswer[] => {\n const entry = draftEntry(draft, index);\n if (!isAnswered(entry)) return [];\n const notes = entry.notes.trim();\n const base = { questionIndex: index, question: question.question, ...(notes === '' ? {} : { notes }) };\n if (entry.custom !== null && entry.custom.trim() !== '') {\n return [{ ...base, kind: 'custom' as const, answer: entry.custom }];\n }\n if (question.multiSelect) {\n return [{ ...base, kind: 'multi' as const, answer: null, selected: [...entry.selected] }];\n }\n return [{ ...base, kind: 'option' as const, answer: entry.selected[0] ?? null }];\n });\n}\n","import type { ToolPromptRenderProps } from '@agimon-ai/po-core/native';\nimport { Button, fontOf, Markdown, SectionLabel, selectionTick, Textarea } from '@agimon-ai/po-native-components';\nimport { useMemo, useState } from 'react';\nimport { Pressable, ScrollView, Text, View } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { encodeAnswerEnvelope } from '../../../../../../types/askUserWire';\nimport {\n chooseOption,\n CUSTOM_LABEL,\n draftAnswers,\n draftEntry,\n emptyDraft,\n isAnswered,\n isComplete,\n nextUnanswered,\n type PromptQuestion,\n type QuestionnaireDraft,\n readPromptQuestions,\n setCustom,\n setNotes,\n} from '../_lib/questionnaireDraft';\n\nexport interface QuestionnaireViewProps {\n questions: readonly PromptQuestion[];\n draft: QuestionnaireDraft;\n current: number;\n previewIndex: number;\n onDraft: (draft: QuestionnaireDraft, settles: boolean) => void;\n onCurrent: (index: number) => void;\n onPreview: (index: number) => void;\n onSubmit: () => void;\n onCancel: () => void;\n}\n\n/** Pure presentation for the approved-target stories and the native prompt host. */\nexport function QuestionnaireView(props: QuestionnaireViewProps) {\n const { questions, draft, current, previewIndex, onDraft, onCurrent, onPreview, onSubmit, onCancel } = props;\n const question = questions[current];\n const entry = draftEntry(draft, current);\n const preview = question?.options[previewIndex]?.preview;\n return (\n <View style={styles.frame} testID=\"questionnaire\">\n <ScrollView horizontal contentContainerStyle={styles.steps} accessibilityLabel=\"Questions\">\n {questions.map((step, index) => (\n <Button\n key={`${String(index)}:${step.question}`}\n size=\"card\"\n variant={index === current ? 'secondary' : 'ghost'}\n aria-selected={index === current}\n accessibilityLabel={`${step.header || `Question ${String(index + 1)}`}, ${isAnswered(draftEntry(draft, index)) ? 'answered' : 'not answered'}`}\n testID={`questionnaire-step-${String(index)}`}\n onPress={() => {\n selectionTick();\n onCurrent(index);\n }}\n >\n {`${isAnswered(draftEntry(draft, index)) ? '✓ ' : ''}${step.header || `Question ${String(index + 1)}`}`}\n </Button>\n ))}\n </ScrollView>\n <ScrollView style={styles.body} contentContainerStyle={styles.content} keyboardShouldPersistTaps=\"handled\">\n {question ? (\n <>\n <Text style={styles.meta}>\n Question {current + 1} / {questions.length}\n {question.multiSelect ? ' · Choose any that fit' : ''}\n </Text>\n <Text style={styles.question} testID=\"questionnaire-question\">\n {question.question}\n </Text>\n {question.options.map((option, index) => {\n const chosen = entry.custom === null && entry.selected.includes(option.label);\n return (\n <Pressable\n key={`${String(index)}:${option.label}`}\n accessibilityRole={question.multiSelect ? 'checkbox' : 'radio'}\n aria-checked={chosen}\n accessibilityLabel={option.label}\n accessibilityHint={option.description}\n testID={`questionnaire-option-${String(index)}`}\n style={[styles.option, chosen && styles.chosen]}\n onFocus={() => onPreview(index)}\n onPress={() => {\n selectionTick();\n onPreview(index);\n onDraft(chooseOption(draft, current, option.label, question.multiSelect), !question.multiSelect);\n }}\n >\n <Text style={[styles.mark, chosen && styles.selectedInk]}>\n {question.multiSelect ? (chosen ? '☑' : '☐') : chosen ? '●' : '○'}\n </Text>\n <View style={styles.optionText}>\n <Text style={styles.label}>{option.label}</Text>\n {option.description ? <Text style={styles.meta}>{option.description}</Text> : null}\n </View>\n </Pressable>\n );\n })}\n <Button\n variant={entry.custom === null ? 'ghost' : 'secondary'}\n size=\"card\"\n testID=\"questionnaire-option-custom\"\n onPress={() => onDraft(setCustom(draft, current, entry.custom ?? ''), false)}\n >\n {CUSTOM_LABEL}\n </Button>\n {entry.custom !== null ? (\n <View style={styles.field}>\n <SectionLabel>Your answer</SectionLabel>\n <Textarea\n autoFocus\n rows={2}\n accessibilityLabel=\"Your answer\"\n testID=\"questionnaire-custom\"\n value={entry.custom}\n placeholder=\"type your answer…\"\n onChangeText={(value) => onDraft(setCustom(draft, current, value), false)}\n />\n </View>\n ) : null}\n {preview && entry.custom === null ? (\n <View style={styles.preview} testID=\"questionnaire-preview\">\n <SectionLabel>Preview</SectionLabel>\n <Markdown text={preview} />\n </View>\n ) : null}\n <View style={styles.field}>\n <SectionLabel>Note (optional)</SectionLabel>\n <Textarea\n rows={1}\n accessibilityLabel=\"Note (optional)\"\n testID=\"questionnaire-notes\"\n value={entry.notes}\n placeholder=\"anything the options do not cover\"\n onChangeText={(value) => onDraft(setNotes(draft, current, value), false)}\n />\n </View>\n </>\n ) : (\n <Text style={styles.meta}>the agent asked something this cockpit could not read</Text>\n )}\n </ScrollView>\n <View style={styles.actions}>\n {questions.length > 1 && !isComplete(draft) ? (\n <Button\n size=\"card\"\n disabled={!isAnswered(entry)}\n onPress={() => onDraft(draft, true)}\n testID=\"questionnaire-next\"\n >\n Next question\n </Button>\n ) : null}\n <Button size=\"card\" onPress={onCancel} testID=\"questionnaire-cancel\">\n cancel\n </Button>\n <Button\n size=\"card\"\n variant=\"primary\"\n disabled={!isComplete(draft)}\n onPress={onSubmit}\n testID=\"questionnaire-submit\"\n >\n submit answers\n </Button>\n </View>\n </View>\n );\n}\n\nfunction Questionnaire({\n questions,\n answer,\n cancel,\n}: {\n questions: readonly PromptQuestion[];\n answer: ToolPromptRenderProps['answer'];\n cancel: ToolPromptRenderProps['cancel'];\n}) {\n const [draft, setDraft] = useState(() => emptyDraft(questions.length));\n const [current, setCurrent] = useState(0);\n const [previewIndex, setPreviewIndex] = useState(0);\n const show = (index: number) => {\n setCurrent(index);\n setPreviewIndex(0);\n };\n return (\n <QuestionnaireView\n questions={questions}\n draft={draft}\n current={current}\n previewIndex={previewIndex}\n onDraft={(next, settles) => {\n setDraft(next);\n if (!settles) return;\n const pending = nextUnanswered(next, current);\n if (pending >= 0 && pending !== current) show(pending);\n }}\n onCurrent={show}\n onPreview={setPreviewIndex}\n onSubmit={() => {\n if (isComplete(draft)) answer(encodeAnswerEnvelope(draftAnswers(questions, draft)));\n }}\n onCancel={cancel}\n />\n );\n}\n\n/** The dialog identity owns the draft, so another session or request cannot inherit its answers. */\nexport function QuestionnairePrompt({ args, sessionId, dialog, answer, cancel }: ToolPromptRenderProps) {\n const questions = useMemo(() => readPromptQuestions(args), [args]);\n return (\n <Questionnaire\n key={`${sessionId}:${dialog.id}:${String(questions.length)}`}\n questions={questions}\n answer={answer}\n cancel={cancel}\n />\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n frame: {\n maxHeight: '75%',\n flexShrink: 1,\n backgroundColor: theme.colors.rail,\n borderTopWidth: 1,\n borderColor: theme.colors['edge-magenta'],\n },\n steps: { gap: theme.spacing(2), padding: theme.spacing(3) },\n body: { flexShrink: 1 },\n content: { gap: theme.spacing(2), paddingHorizontal: theme.spacing(3), paddingBottom: theme.spacing(3) },\n question: {\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.base,\n lineHeight: theme.lineHeightRelaxed.base,\n color: theme.colors.hi,\n },\n meta: {\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeightRelaxed.sm,\n color: theme.colors.dim,\n },\n option: {\n flexDirection: 'row',\n gap: theme.spacing(3),\n minHeight: 44,\n padding: theme.spacing(3),\n borderWidth: 1,\n borderColor: theme.colors['border-soft'],\n borderRadius: theme.borderRadius.md,\n },\n chosen: { borderColor: theme.colors['edge-magenta'], backgroundColor: theme.colors['tint-magenta'] },\n mark: { fontFamily: fontOf(theme.fontFamily.sans), fontSize: theme.fontSize.lg, color: theme.colors.dim },\n selectedInk: { color: theme.colors.magenta },\n optionText: { flex: 1, gap: theme.spacing(1) },\n label: {\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.base,\n lineHeight: theme.lineHeightRelaxed.base,\n fontWeight: theme.fontWeight.semibold,\n color: theme.colors.text,\n },\n field: { gap: theme.spacing(2) },\n preview: {\n gap: theme.spacing(2),\n paddingLeft: theme.spacing(3),\n borderLeftWidth: 2,\n borderColor: theme.colors['edge-magenta'],\n },\n actions: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n justifyContent: 'flex-end',\n gap: theme.spacing(2),\n padding: theme.spacing(3),\n borderTopWidth: 1,\n borderColor: theme.colors['border-soft'],\n },\n}));\n","import { defineToolRenderer } from '@agimon-ai/po-core/native';\n\nimport { AskUserQuestionToolMessage } from './_components/AskUserQuestionToolMessage.native';\nimport { QuestionnairePrompt } from './_components/QuestionnairePrompt.native';\nimport { readPromptQuestions } from './_lib/questionnaireDraft';\n\nexport default defineToolRenderer({\n message: AskUserQuestionToolMessage,\n prompt: {\n claims: (dialog, args) =>\n dialog.method === 'select' &&\n readPromptQuestions(args).some((question) => dialog.title.startsWith(question.question)),\n component: QuestionnairePrompt,\n },\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineNativePlugin, type WebPluginContributions } from '@agimon-ai/po-core/native';\n\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.native';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const nativePlugin = defineNativePlugin({\n id: 'ask-user',\n session: {\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['ask_user_question'] }, toolAskUserQuestion)]; },\n },\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/po-user-feedback",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.2",
|
|
4
4
|
"description": "Structured user questions with interactive and autonomous Voice handoff for Pi agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"import": "./dist/apiContracts.mjs",
|
|
50
50
|
"require": "./dist/apiContracts.cjs"
|
|
51
51
|
},
|
|
52
|
+
"./extensions/native": {
|
|
53
|
+
"import": "./dist/extensions/native.mjs"
|
|
54
|
+
},
|
|
52
55
|
"./extensions/server": {
|
|
53
56
|
"types": "./dist/extensions/server.d.mts",
|
|
54
57
|
"import": "./dist/extensions/server.mjs",
|
|
@@ -63,16 +66,17 @@
|
|
|
63
66
|
"access": "public"
|
|
64
67
|
},
|
|
65
68
|
"dependencies": {
|
|
66
|
-
"@agimon-ai/po-core": "0.0.0-alpha.
|
|
67
|
-
"@agimon-ai/po-minor-mode": "0.0.0-alpha.
|
|
68
|
-
"@agimon-ai/po-web-components": "0.0.0-alpha.
|
|
69
|
+
"@agimon-ai/po-core": "0.0.0-alpha.2",
|
|
70
|
+
"@agimon-ai/po-minor-mode": "0.0.0-alpha.2",
|
|
71
|
+
"@agimon-ai/po-web-components": "0.0.0-alpha.2",
|
|
69
72
|
"@deepseek-ai/cordis": "4.0.4",
|
|
70
73
|
"typebox": "1.3.34"
|
|
71
74
|
},
|
|
72
75
|
"devDependencies": {
|
|
73
|
-
"@agimon-ai/po-build": "0.0.0-alpha.
|
|
74
|
-
"@agimon-ai/po-
|
|
75
|
-
"@
|
|
76
|
+
"@agimon-ai/po-build": "0.0.0-alpha.2",
|
|
77
|
+
"@agimon-ai/po-native-components": "0.0.0-alpha.2",
|
|
78
|
+
"@agimon-ai/po-voice": "0.0.0-alpha.2",
|
|
79
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
76
80
|
"@tanstack/react-store": "0.11.2",
|
|
77
81
|
"@tanstack/store": "0.11.2",
|
|
78
82
|
"@types/node": "26.6.4",
|
|
@@ -80,21 +84,43 @@
|
|
|
80
84
|
"@vitest/coverage-v8": "5.0.3",
|
|
81
85
|
"react": "19.3.0",
|
|
82
86
|
"react-dom": "19.3.0",
|
|
87
|
+
"react-native": "0.86.3",
|
|
88
|
+
"react-native-unistyles": "3.5.2",
|
|
83
89
|
"tsdown": "0.23.0",
|
|
84
90
|
"typescript": "7.0.2",
|
|
85
91
|
"vitest": "5.0.3"
|
|
86
92
|
},
|
|
87
93
|
"peerDependencies": {
|
|
88
|
-
"@
|
|
94
|
+
"@agimon-ai/po-native-components": "0.0.0-alpha.2",
|
|
95
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
96
|
+
"react-native": "0.86.3",
|
|
97
|
+
"react-native-unistyles": "3.5.2"
|
|
89
98
|
},
|
|
90
99
|
"peerDependenciesMeta": {
|
|
100
|
+
"@agimon-ai/po-native-components": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
91
103
|
"@earendil-works/pi-coding-agent": {
|
|
92
104
|
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"react-native": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"react-native-unistyles": {
|
|
110
|
+
"optional": true
|
|
93
111
|
}
|
|
94
112
|
},
|
|
95
113
|
"engines": {
|
|
96
114
|
"node": ">=24"
|
|
97
115
|
},
|
|
116
|
+
"poNative": {
|
|
117
|
+
"pluginId": "ask-user",
|
|
118
|
+
"channels": [],
|
|
119
|
+
"client": "./dist/extensions/native.mjs",
|
|
120
|
+
"scopes": [
|
|
121
|
+
"session"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
98
124
|
"poServer": {
|
|
99
125
|
"entry": "./generated/server.ts",
|
|
100
126
|
"dist": "./dist/extensions/server.mjs",
|