@vitos-pizza/vitos-pizza 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +42 -11
- package/README.md +31 -4
- package/assets/architecture.png +0 -0
- package/assets/architecture.svg +97 -0
- package/package.json +22 -12
- package/packages/agent-mode/package.json +3 -3
- package/packages/agent-mode/src/plan-instructions.ts +19 -7
- package/packages/hypa/extensions/index.ts +16 -0
- package/packages/hypa/package.json +23 -0
- package/packages/hypa/scripts/smoke.mjs +50 -0
- package/packages/hypa/src/config.ts +37 -0
- package/packages/hypa/src/seed.ts +22 -0
- package/packages/hypa/tsconfig.json +13 -0
- package/packages/keybindings/package.json +1 -1
- package/packages/permission-system/package.json +2 -2
- package/packages/permission-system/presets/default.json +8 -0
- package/packages/permission-system/presets/plan.json +6 -0
- package/packages/permission-system/presets/yolo.json +1 -0
- package/packages/question/extensions/index.ts +54 -21
- package/packages/question/package.json +1 -1
- package/packages/question/src/forwarding/file-watcher.ts +41 -11
- package/packages/question/src/forwarding/forwarder.ts +61 -27
- package/packages/question/src/question-ui.ts +773 -78
- package/packages/question/src/register-tool.ts +351 -40
- package/packages/question/src/types.ts +79 -7
- package/packages/session-title/package.json +3 -3
- package/packages/settings-preset/package.json +1 -1
- package/packages/subagents/agents/planner.md +3 -2
- package/packages/subagents/agents/scout.md +2 -1
- package/packages/subagents/agents/worker.md +3 -2
- package/packages/subagents/package.json +1 -1
- package/packages/todoist/package.json +1 -1
- package/packages/ui-enhancements/package.json +1 -1
- package/packages/ui-enhancements/src/chrome/resize-recovery.ts +71 -14
- package/packages/websearch/package.json +1 -1
- package/scripts/sync-pi-manifest.mjs +18 -12
|
@@ -7,8 +7,25 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
7
7
|
import { Type } from "typebox";
|
|
8
8
|
import { forwardQuestionPrompt } from "./forwarding/forwarder.ts";
|
|
9
9
|
import { isSubagentChild, resolveParentSessionId } from "./parent-session.ts";
|
|
10
|
-
import { runQuestionUi } from "./question-ui.ts";
|
|
11
|
-
import type {
|
|
10
|
+
import { runMultiQuestionUi, runQuestionUi } from "./question-ui.ts";
|
|
11
|
+
import type {
|
|
12
|
+
MultiQuestionDetails,
|
|
13
|
+
MultiQuestionParams,
|
|
14
|
+
MultiTabAnswer,
|
|
15
|
+
QuestionDetails,
|
|
16
|
+
QuestionParams,
|
|
17
|
+
QuestionUiResult,
|
|
18
|
+
QuestionsParams,
|
|
19
|
+
SelectType,
|
|
20
|
+
SingleTabAnswer,
|
|
21
|
+
TabAnswer,
|
|
22
|
+
} from "./types.ts";
|
|
23
|
+
|
|
24
|
+
const SelectTypeSchema = Type.Optional(
|
|
25
|
+
Type.Union([Type.Literal("single"), Type.Literal("multi")], {
|
|
26
|
+
description: '"single" (default) or "multi" — allows multiple selections',
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
12
29
|
|
|
13
30
|
const OptionSchema = Type.Object({
|
|
14
31
|
label: Type.String({ description: "Display label for the option" }),
|
|
@@ -17,11 +34,44 @@ const OptionSchema = Type.Object({
|
|
|
17
34
|
),
|
|
18
35
|
});
|
|
19
36
|
|
|
20
|
-
const
|
|
37
|
+
const QuestionTabSchema = Type.Object({
|
|
38
|
+
id: Type.Optional(
|
|
39
|
+
Type.String({
|
|
40
|
+
description:
|
|
41
|
+
"Optional stable identifier used as result key (defaults to q0, q1, …)",
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
title: Type.Optional(
|
|
45
|
+
Type.String({
|
|
46
|
+
description: "Optional tab bar label (defaults to Q1, Q2, …)",
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
21
49
|
question: Type.String({ description: "The question to ask the user" }),
|
|
22
50
|
options: Type.Array(OptionSchema, {
|
|
23
51
|
description: "Options for the user to choose from",
|
|
24
52
|
}),
|
|
53
|
+
selectType: SelectTypeSchema,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const QuestionParamsSchema = Type.Object({
|
|
57
|
+
question: Type.Optional(
|
|
58
|
+
Type.String({
|
|
59
|
+
description: "The question to ask the user (single‑question mode)",
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
options: Type.Optional(
|
|
63
|
+
Type.Array(OptionSchema, {
|
|
64
|
+
description: "Options for the user to choose from (single‑question mode)",
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
selectType: SelectTypeSchema,
|
|
68
|
+
questions: Type.Optional(
|
|
69
|
+
Type.Array(QuestionTabSchema, {
|
|
70
|
+
description:
|
|
71
|
+
"Multiple questions displayed as tabs (multi‑question mode). Overrides question/options when present.",
|
|
72
|
+
minItems: 1,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
25
75
|
});
|
|
26
76
|
|
|
27
77
|
export interface QuestionToolDeps {
|
|
@@ -32,6 +82,8 @@ export interface QuestionToolDeps {
|
|
|
32
82
|
};
|
|
33
83
|
}
|
|
34
84
|
|
|
85
|
+
// ── result builders (single) ──
|
|
86
|
+
|
|
35
87
|
function buildCancelledResult(
|
|
36
88
|
params: QuestionParams,
|
|
37
89
|
): AgentToolResult<QuestionDetails> {
|
|
@@ -48,34 +100,72 @@ function buildCancelledResult(
|
|
|
48
100
|
|
|
49
101
|
function buildAnswerResult(
|
|
50
102
|
params: QuestionParams,
|
|
51
|
-
|
|
52
|
-
wasCustom: boolean,
|
|
53
|
-
index?: number,
|
|
103
|
+
answerData: string | SingleTabAnswer | MultiTabAnswer | QuestionUiResult,
|
|
54
104
|
): AgentToolResult<QuestionDetails> {
|
|
55
105
|
const simpleOptions = params.options.map((o) => o.label);
|
|
56
|
-
|
|
106
|
+
|
|
107
|
+
// Check for multi-select (from MultiTabAnswer or QuestionUiResult)
|
|
108
|
+
if (typeof answerData === "object" && ("answers" in answerData || "multiAnswers" in answerData)) {
|
|
109
|
+
const multiAnswers = "answers" in answerData
|
|
110
|
+
? (answerData as MultiTabAnswer).answers
|
|
111
|
+
: (answerData as QuestionUiResult).multiAnswers ?? [];
|
|
112
|
+
const multiIndices = "indices" in answerData
|
|
113
|
+
? (answerData as MultiTabAnswer).indices
|
|
114
|
+
: (answerData as QuestionUiResult).multiIndices ?? [];
|
|
115
|
+
|
|
116
|
+
const lines = multiAnswers.map((a: string, i: number) => {
|
|
117
|
+
const idx = multiIndices[i];
|
|
118
|
+
return idx === -1 ? ` (custom) ${a}` : ` ${idx}. ${a}`;
|
|
119
|
+
});
|
|
57
120
|
return {
|
|
58
|
-
content: [{ type: "text", text: `User
|
|
121
|
+
content: [{ type: "text", text: `User selected:\n${lines.join("\n")}` }],
|
|
59
122
|
details: {
|
|
60
123
|
question: params.question,
|
|
61
124
|
options: simpleOptions,
|
|
62
|
-
answer,
|
|
63
|
-
|
|
125
|
+
answer: multiAnswers.join(", "),
|
|
126
|
+
multiAnswers,
|
|
127
|
+
multiIndices,
|
|
64
128
|
},
|
|
65
129
|
};
|
|
66
130
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
131
|
+
|
|
132
|
+
if (typeof answerData === "object") {
|
|
133
|
+
// Single select
|
|
134
|
+
const single = answerData as SingleTabAnswer;
|
|
135
|
+
if (single.wasCustom) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: `User wrote: ${single.answer}` }],
|
|
138
|
+
details: {
|
|
139
|
+
question: params.question,
|
|
140
|
+
options: simpleOptions,
|
|
141
|
+
answer: single.answer,
|
|
142
|
+
wasCustom: true,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: `User selected: ${single.index ?? simpleOptions.indexOf(single.answer) + 1}. ${single.answer}`,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
details: {
|
|
154
|
+
question: params.question,
|
|
155
|
+
options: simpleOptions,
|
|
156
|
+
answer: single.answer,
|
|
157
|
+
wasCustom: false,
|
|
72
158
|
},
|
|
73
|
-
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Legacy string
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text: `User selected: ${answerData}` }],
|
|
74
165
|
details: {
|
|
75
166
|
question: params.question,
|
|
76
167
|
options: simpleOptions,
|
|
77
|
-
answer,
|
|
78
|
-
wasCustom: false,
|
|
168
|
+
answer: answerData,
|
|
79
169
|
},
|
|
80
170
|
};
|
|
81
171
|
}
|
|
@@ -94,7 +184,75 @@ function buildUnavailableResult(
|
|
|
94
184
|
};
|
|
95
185
|
}
|
|
96
186
|
|
|
97
|
-
|
|
187
|
+
// ── result builders (multi) ──
|
|
188
|
+
|
|
189
|
+
function buildMultiCancelledResult(
|
|
190
|
+
params: MultiQuestionParams,
|
|
191
|
+
): AgentToolResult<MultiQuestionDetails> {
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{ type: "text", text: "User cancelled the multi‑question selection" },
|
|
195
|
+
],
|
|
196
|
+
details: {
|
|
197
|
+
answers: {},
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildMultiAnswerResult(
|
|
203
|
+
params: MultiQuestionParams,
|
|
204
|
+
answers: Record<string, TabAnswer>,
|
|
205
|
+
): AgentToolResult<MultiQuestionDetails> {
|
|
206
|
+
const lines: string[] = ["User answered:"];
|
|
207
|
+
for (const [key, ans] of Object.entries(answers)) {
|
|
208
|
+
const tab = params.questions.find(
|
|
209
|
+
(q) => (q.id ?? `q${params.questions.indexOf(q)}`) === key,
|
|
210
|
+
);
|
|
211
|
+
const label = tab?.title ?? key;
|
|
212
|
+
if ("answers" in ans) {
|
|
213
|
+
const multi = ans as MultiTabAnswer;
|
|
214
|
+
const parts = multi.answers.map((a, i) => {
|
|
215
|
+
const idx = multi.indices[i];
|
|
216
|
+
return idx === -1 ? `${a}(custom)` : `${idx}. ${a}`;
|
|
217
|
+
});
|
|
218
|
+
lines.push(` ${label}: ${parts.join(", ")}`);
|
|
219
|
+
} else {
|
|
220
|
+
const single = ans as SingleTabAnswer;
|
|
221
|
+
if (single.wasCustom) {
|
|
222
|
+
lines.push(` ${label}: ${single.answer} (custom)`);
|
|
223
|
+
} else {
|
|
224
|
+
lines.push(` ${label}: ${single.index}. ${single.answer}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
230
|
+
details: { answers },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildMultiUnavailableResult(
|
|
235
|
+
params: MultiQuestionParams,
|
|
236
|
+
reason: string,
|
|
237
|
+
): AgentToolResult<MultiQuestionDetails> {
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: reason }],
|
|
240
|
+
details: { answers: {} },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── execution ──
|
|
245
|
+
|
|
246
|
+
function isMultiParams(
|
|
247
|
+
params: QuestionsParams,
|
|
248
|
+
): params is MultiQuestionParams {
|
|
249
|
+
return (
|
|
250
|
+
"questions" in params &&
|
|
251
|
+
Array.isArray((params as MultiQuestionParams).questions)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function executeSingleQuestion(
|
|
98
256
|
params: QuestionParams,
|
|
99
257
|
ctx: ExtensionContext,
|
|
100
258
|
deps: QuestionToolDeps,
|
|
@@ -110,12 +268,13 @@ async function executeQuestion(
|
|
|
110
268
|
if (!child && ctx.hasUI) {
|
|
111
269
|
const result = await runQuestionUi(ctx.ui, params);
|
|
112
270
|
if (!result) return buildCancelledResult(params);
|
|
113
|
-
|
|
114
|
-
params,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
271
|
+
if (result.multiAnswers && result.multiIndices) {
|
|
272
|
+
return buildAnswerResult(params, {
|
|
273
|
+
answers: result.multiAnswers,
|
|
274
|
+
indices: result.multiIndices,
|
|
275
|
+
} as MultiTabAnswer);
|
|
276
|
+
}
|
|
277
|
+
return buildAnswerResult(params, result as SingleTabAnswer);
|
|
119
278
|
}
|
|
120
279
|
|
|
121
280
|
if (!parentSessionId) {
|
|
@@ -140,17 +299,83 @@ async function executeQuestion(
|
|
|
140
299
|
);
|
|
141
300
|
}
|
|
142
301
|
|
|
143
|
-
if (forwarded.cancelled
|
|
302
|
+
if (forwarded.cancelled) {
|
|
144
303
|
return buildCancelledResult(params);
|
|
145
304
|
}
|
|
146
305
|
|
|
147
|
-
|
|
306
|
+
// Multi-select via forwarding
|
|
307
|
+
if (forwarded.multiAnswers && forwarded.multiIndices) {
|
|
308
|
+
return buildAnswerResult(params, {
|
|
309
|
+
answers: forwarded.multiAnswers,
|
|
310
|
+
indices: forwarded.multiIndices,
|
|
311
|
+
} as MultiTabAnswer);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Single-select via forwarding
|
|
315
|
+
if (forwarded.answer !== null && forwarded.answer !== undefined) {
|
|
316
|
+
return buildAnswerResult(params, {
|
|
317
|
+
answer: forwarded.answer,
|
|
318
|
+
wasCustom: forwarded.wasCustom ?? false,
|
|
319
|
+
} as SingleTabAnswer);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return buildCancelledResult(params);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function executeMultiQuestion(
|
|
326
|
+
params: MultiQuestionParams,
|
|
327
|
+
ctx: ExtensionContext,
|
|
328
|
+
deps: QuestionToolDeps,
|
|
329
|
+
): Promise<AgentToolResult<MultiQuestionDetails>> {
|
|
330
|
+
if (params.questions.length === 0) {
|
|
331
|
+
return buildMultiUnavailableResult(params, "Error: No questions provided");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const child = isSubagentChild();
|
|
335
|
+
const parentSessionId = resolveParentSessionId();
|
|
336
|
+
const currentSessionId = ctx.sessionManager.getSessionId?.() ?? null;
|
|
337
|
+
|
|
338
|
+
if (!child && ctx.hasUI) {
|
|
339
|
+
const result = await runMultiQuestionUi(ctx.ui, params);
|
|
340
|
+
if (!result) return buildMultiCancelledResult(params);
|
|
341
|
+
return buildMultiAnswerResult(params, result.answers);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!parentSessionId) {
|
|
345
|
+
return buildMultiUnavailableResult(
|
|
346
|
+
params,
|
|
347
|
+
"Error: UI not available (running in non-interactive mode)",
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const forwarded = await forwardQuestionPrompt({
|
|
352
|
+
events: deps.events,
|
|
353
|
+
agentDir: deps.getAgentDir(),
|
|
354
|
+
targetSessionId: parentSessionId,
|
|
355
|
+
requesterSessionId: currentSessionId ?? "unknown",
|
|
148
356
|
params,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!forwarded) {
|
|
360
|
+
return buildMultiUnavailableResult(
|
|
361
|
+
params,
|
|
362
|
+
"Error: Question prompt timed out or parent session unavailable",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (forwarded.cancelled) {
|
|
367
|
+
return buildMultiCancelledResult(params);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (forwarded.answers) {
|
|
371
|
+
return buildMultiAnswerResult(params, forwarded.answers);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return buildMultiUnavailableResult(params, "Error: No answers returned");
|
|
152
375
|
}
|
|
153
376
|
|
|
377
|
+
// ── tool registration ──
|
|
378
|
+
|
|
154
379
|
export function registerQuestionTool(
|
|
155
380
|
pi: ExtensionAPI,
|
|
156
381
|
deps: QuestionToolDeps,
|
|
@@ -159,12 +384,26 @@ export function registerQuestionTool(
|
|
|
159
384
|
name: "question",
|
|
160
385
|
label: "Question",
|
|
161
386
|
description:
|
|
162
|
-
"Ask the user a question and let them pick from options. Use when you need user input to proceed.",
|
|
387
|
+
"Ask the user a question and let them pick from options. Supports single questions (question+options) or multiple tabbed questions (questions[]). Use when you need user input to proceed.",
|
|
163
388
|
parameters: QuestionParamsSchema,
|
|
164
389
|
executionMode: "sequential",
|
|
165
390
|
|
|
166
391
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
167
|
-
|
|
392
|
+
const p = params as QuestionsParams;
|
|
393
|
+
if (isMultiParams(p)) {
|
|
394
|
+
return executeMultiQuestion(p, ctx, deps);
|
|
395
|
+
}
|
|
396
|
+
const single = p as QuestionParams;
|
|
397
|
+
if (!single.question || !single.options) {
|
|
398
|
+
return buildUnavailableResult(
|
|
399
|
+
{
|
|
400
|
+
question: single.question ?? "",
|
|
401
|
+
options: single.options ?? [],
|
|
402
|
+
},
|
|
403
|
+
"Error: Provide either question+options or questions[]",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return executeSingleQuestion(single, ctx, deps);
|
|
168
407
|
},
|
|
169
408
|
|
|
170
409
|
renderCall(_args, theme, _context) {
|
|
@@ -172,19 +411,56 @@ export function registerQuestionTool(
|
|
|
172
411
|
},
|
|
173
412
|
|
|
174
413
|
renderResult(result, _options, theme, _context) {
|
|
175
|
-
const details = result.details as
|
|
414
|
+
const details = result.details as
|
|
415
|
+
| QuestionDetails
|
|
416
|
+
| MultiQuestionDetails
|
|
417
|
+
| undefined;
|
|
176
418
|
if (!details) {
|
|
177
419
|
const text = result.content[0];
|
|
178
420
|
const fallback = text?.type === "text" ? text.text : "";
|
|
179
421
|
return new Text(fallback, 0, 0);
|
|
180
422
|
}
|
|
181
423
|
|
|
182
|
-
|
|
183
|
-
|
|
424
|
+
// Multi-question (tabbed) result
|
|
425
|
+
if ("answers" in details && details.answers) {
|
|
426
|
+
const ans = details.answers;
|
|
427
|
+
const keys = Object.keys(ans);
|
|
428
|
+
if (keys.length === 0) {
|
|
429
|
+
return new Text(theme.fg("warning", "Cancelled"), 0, 0);
|
|
430
|
+
}
|
|
431
|
+
const first = ans[keys[0]!];
|
|
432
|
+
if (keys.length === 1 && first) {
|
|
433
|
+
const display = "answers" in first
|
|
434
|
+
? (first as MultiTabAnswer).answers.join(", ")
|
|
435
|
+
: (first as SingleTabAnswer).answer;
|
|
436
|
+
return new Text(
|
|
437
|
+
theme.fg("success", "✓ ") + theme.fg("accent", display),
|
|
438
|
+
0,
|
|
439
|
+
0,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
return new Text(
|
|
443
|
+
theme.fg("success", `✓ ${keys.length} answered`),
|
|
444
|
+
0,
|
|
445
|
+
0,
|
|
446
|
+
);
|
|
184
447
|
}
|
|
185
448
|
|
|
449
|
+
// Single-question result
|
|
450
|
+
const sq = details as QuestionDetails;
|
|
451
|
+
if (sq.multiAnswers && sq.multiAnswers.length > 0) {
|
|
452
|
+
return new Text(
|
|
453
|
+
theme.fg("success", "✓ ") +
|
|
454
|
+
theme.fg("accent", sq.multiAnswers.join(", ")),
|
|
455
|
+
0,
|
|
456
|
+
0,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (sq.answer === null || sq.answer === undefined) {
|
|
460
|
+
return new Text(theme.fg("warning", "Cancelled"), 0, 0);
|
|
461
|
+
}
|
|
186
462
|
return new Text(
|
|
187
|
-
theme.fg("success", "✓ ") + theme.fg("accent",
|
|
463
|
+
theme.fg("success", "✓ ") + theme.fg("accent", sq.answer),
|
|
188
464
|
0,
|
|
189
465
|
0,
|
|
190
466
|
);
|
|
@@ -192,26 +468,61 @@ export function registerQuestionTool(
|
|
|
192
468
|
});
|
|
193
469
|
}
|
|
194
470
|
|
|
471
|
+
// ── local prompt helper ──
|
|
472
|
+
|
|
195
473
|
export async function promptQuestionLocally(
|
|
196
474
|
ui: ExtensionContext["ui"],
|
|
197
|
-
params: QuestionParams,
|
|
475
|
+
params: QuestionParams | MultiQuestionParams,
|
|
198
476
|
responderSessionId: string,
|
|
199
477
|
) {
|
|
200
|
-
|
|
478
|
+
if (isMultiParams(params)) {
|
|
479
|
+
const result = await runMultiQuestionUi(ui, params);
|
|
480
|
+
if (!result) {
|
|
481
|
+
return {
|
|
482
|
+
answers: {},
|
|
483
|
+
cancelled: true,
|
|
484
|
+
responderSessionId,
|
|
485
|
+
respondedAt: Date.now(),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
answers: result.answers,
|
|
490
|
+
cancelled: false,
|
|
491
|
+
responderSessionId,
|
|
492
|
+
respondedAt: Date.now(),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
201
496
|
const result = await runQuestionUi(ui, params);
|
|
202
497
|
if (!result) {
|
|
203
498
|
return {
|
|
204
499
|
question: params.question,
|
|
205
|
-
options:
|
|
500
|
+
options: params.options.map((o) => o.label),
|
|
206
501
|
answer: null,
|
|
207
502
|
cancelled: true,
|
|
208
503
|
responderSessionId,
|
|
209
504
|
respondedAt: Date.now(),
|
|
210
505
|
};
|
|
211
506
|
}
|
|
507
|
+
|
|
508
|
+
// Multi-select result
|
|
509
|
+
if (result.multiAnswers && result.multiIndices) {
|
|
510
|
+
return {
|
|
511
|
+
question: params.question,
|
|
512
|
+
options: params.options.map((o) => o.label),
|
|
513
|
+
answer: result.multiAnswers.join(", "),
|
|
514
|
+
multiAnswers: result.multiAnswers,
|
|
515
|
+
multiIndices: result.multiIndices,
|
|
516
|
+
cancelled: false,
|
|
517
|
+
responderSessionId,
|
|
518
|
+
respondedAt: Date.now(),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Single-select result
|
|
212
523
|
return {
|
|
213
524
|
question: params.question,
|
|
214
|
-
options:
|
|
525
|
+
options: params.options.map((o) => o.label),
|
|
215
526
|
answer: result.answer,
|
|
216
527
|
wasCustom: result.wasCustom,
|
|
217
528
|
cancelled: false,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type SelectType = "single" | "multi";
|
|
2
|
+
|
|
1
3
|
export interface QuestionOption {
|
|
2
4
|
label: string;
|
|
3
5
|
description?: string;
|
|
@@ -6,35 +8,105 @@ export interface QuestionOption {
|
|
|
6
8
|
export interface QuestionParams {
|
|
7
9
|
question: string;
|
|
8
10
|
options: QuestionOption[];
|
|
11
|
+
selectType?: SelectType;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
// --- Multi-question (tabs) types ---
|
|
15
|
+
|
|
16
|
+
export interface QuestionTab {
|
|
17
|
+
/** Optional stable identifier used as result key; defaults to "q0", "q1", … */
|
|
18
|
+
id?: string;
|
|
19
|
+
/** Tab bar label; defaults to "Q1", "Q2", … */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** The question text shown inside the tab */
|
|
12
22
|
question: string;
|
|
13
|
-
options:
|
|
23
|
+
options: QuestionOption[];
|
|
24
|
+
/** "single" (default) or "multi" */
|
|
25
|
+
selectType?: SelectType;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MultiQuestionParams {
|
|
29
|
+
questions: QuestionTab[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Answer types ---
|
|
33
|
+
|
|
34
|
+
/** Single-select answer (one pick) */
|
|
35
|
+
export interface SingleTabAnswer {
|
|
36
|
+
answer: string;
|
|
37
|
+
wasCustom: boolean;
|
|
38
|
+
index?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Multi-select answer (multiple picks) */
|
|
42
|
+
export interface MultiTabAnswer {
|
|
43
|
+
answers: string[];
|
|
44
|
+
indices: number[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type TabAnswer = SingleTabAnswer | MultiTabAnswer;
|
|
48
|
+
|
|
49
|
+
export type QuestionsParams = QuestionParams | MultiQuestionParams;
|
|
50
|
+
|
|
51
|
+
/** Returns true if the answer is a multi-select answer */
|
|
52
|
+
export function isMultiAnswer(ans: TabAnswer): ans is MultiTabAnswer {
|
|
53
|
+
return "answers" in ans;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- Result types ---
|
|
57
|
+
|
|
58
|
+
export interface QuestionDetails {
|
|
59
|
+
question?: string;
|
|
60
|
+
options?: string[];
|
|
14
61
|
answer: string | null;
|
|
15
62
|
wasCustom?: boolean;
|
|
63
|
+
/** Multi-select answers (present when selectType="multi") */
|
|
64
|
+
multiAnswers?: string[];
|
|
65
|
+
multiIndices?: number[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface MultiQuestionDetails {
|
|
69
|
+
answers: Record<string, TabAnswer>;
|
|
16
70
|
}
|
|
17
71
|
|
|
18
72
|
export interface QuestionUiResult {
|
|
19
73
|
answer: string;
|
|
20
74
|
wasCustom: boolean;
|
|
21
75
|
index?: number;
|
|
76
|
+
/** Multi-select answers (present when selectType="multi") */
|
|
77
|
+
multiAnswers?: string[];
|
|
78
|
+
multiIndices?: number[];
|
|
22
79
|
}
|
|
23
80
|
|
|
81
|
+
export interface MultiQuestionUiResult {
|
|
82
|
+
answers: Record<string, TabAnswer>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- Forwarding types ---
|
|
86
|
+
|
|
24
87
|
export interface ForwardedQuestionRequest {
|
|
25
88
|
id: string;
|
|
26
89
|
createdAt: number;
|
|
27
90
|
requesterSessionId: string;
|
|
28
91
|
targetSessionId: string;
|
|
29
|
-
question
|
|
30
|
-
|
|
92
|
+
/** Single question (legacy) */
|
|
93
|
+
question?: string;
|
|
94
|
+
options?: QuestionOption[];
|
|
95
|
+
selectType?: SelectType;
|
|
96
|
+
/** Multi-question (new) */
|
|
97
|
+
questions?: QuestionTab[];
|
|
31
98
|
}
|
|
32
99
|
|
|
33
100
|
export interface ForwardedQuestionResponse {
|
|
34
|
-
question
|
|
35
|
-
options
|
|
36
|
-
answer
|
|
101
|
+
question?: string;
|
|
102
|
+
options?: string[];
|
|
103
|
+
answer?: string | null;
|
|
37
104
|
wasCustom?: boolean;
|
|
105
|
+
/** Multi-select answers */
|
|
106
|
+
multiAnswers?: string[];
|
|
107
|
+
multiIndices?: number[];
|
|
108
|
+
/** Multi-question answers (each value can be single or multi answer) */
|
|
109
|
+
answers?: Record<string, TabAnswer>;
|
|
38
110
|
cancelled?: boolean;
|
|
39
111
|
responderSessionId: string;
|
|
40
112
|
respondedAt: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitos-pizza/session-title",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Async session auto-naming for Vito's Pizzeria Pi distribution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@vitos-pizza/subagents": "0.
|
|
14
|
+
"@vitos-pizza/subagents": "0.4.1"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "vitest run",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@earendil-works/pi-ai": "*",
|
|
32
32
|
"@earendil-works/pi-coding-agent": "*",
|
|
33
|
-
"@vitos-pizza/subagents": "0.
|
|
33
|
+
"@vitos-pizza/subagents": "0.4.1",
|
|
34
34
|
"typebox": "*",
|
|
35
35
|
"vitest": "^3.2.4"
|
|
36
36
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: planner
|
|
3
3
|
description: Creates implementation plans from context and requirements
|
|
4
|
-
tools:
|
|
4
|
+
tools: hypa_read, hypa_grep, hypa_find, hypa_ls, write, question, web_search, web_read
|
|
5
5
|
thinking: high
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
---
|
|
@@ -10,6 +10,7 @@ You are planner: turn requirements and code context into a concrete implementati
|
|
|
10
10
|
|
|
11
11
|
Rules:
|
|
12
12
|
- Read provided context (and more code as needed) before planning.
|
|
13
|
+
- Use `hypa_read` / `hypa_grep` / `hypa_find` / `hypa_ls` for codebase exploration.
|
|
13
14
|
- Name exact files; prefer small, ordered, actionable tasks; call out risks and dependencies.
|
|
14
15
|
- Underspecified or mutually exclusive choices → use `question` before guessing.
|
|
15
16
|
- Web tools only for current external facts outside the repo.
|
|
@@ -39,4 +40,4 @@ Anything likely to go wrong or need careful verification.
|
|
|
39
40
|
## 功能验收
|
|
40
41
|
3–6 checkboxes of observable outcomes (yes/no verifiable).
|
|
41
42
|
|
|
42
|
-
Keep the plan concrete enough for another agent to execute without guessing. The parent may add a short **
|
|
43
|
+
Keep the plan concrete enough for another agent to execute without guessing. The parent may add a short **Worth considering** footer for gaps or adjacent capabilities — do not write a long next-steps essay.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scout
|
|
3
3
|
description: Fast codebase recon that returns compressed context for handoff
|
|
4
|
-
tools:
|
|
4
|
+
tools: hypa_read, hypa_grep, hypa_find, hypa_ls, question, web_search, web_read
|
|
5
5
|
thinking: low
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
---
|
|
@@ -11,6 +11,7 @@ You are scout: fast codebase recon for handoff. Do not guess. Prefer targeted se
|
|
|
11
11
|
Return the minimum context another agent needs: entry points, key types/APIs, data flow, files likely to change, risks and open questions.
|
|
12
12
|
|
|
13
13
|
Rules:
|
|
14
|
+
- Use `hypa_read` / `hypa_grep` / `hypa_find` / `hypa_ls` for codebase exploration.
|
|
14
15
|
- Cite exact file paths and line ranges.
|
|
15
16
|
- Use `question` only when ambiguity blocks recon; prefer structured options over free-text choices.
|
|
16
17
|
- Web tools only for current external facts outside the repo.
|