@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.
@@ -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.1",
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.1",
67
- "@agimon-ai/po-minor-mode": "0.0.0-alpha.1",
68
- "@agimon-ai/po-web-components": "0.0.0-alpha.1",
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.1",
74
- "@agimon-ai/po-voice": "0.0.0-alpha.1",
75
- "@earendil-works/pi-coding-agent": "1.0.2",
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
- "@earendil-works/pi-coding-agent": "1.0.2"
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",