@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
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
"grep": "allow",
|
|
6
6
|
"find": "allow",
|
|
7
7
|
"ls": "allow",
|
|
8
|
+
"hypa_read": "allow",
|
|
9
|
+
"hypa_grep": "allow",
|
|
10
|
+
"hypa_find": "allow",
|
|
11
|
+
"hypa_ls": "allow",
|
|
8
12
|
"question": "allow",
|
|
9
13
|
"subagent": "allow",
|
|
10
14
|
"wait": "allow",
|
|
11
15
|
"web_search": "ask",
|
|
12
16
|
"web_read": "ask",
|
|
13
17
|
"bash": "deny",
|
|
18
|
+
"hypa_shell": "deny",
|
|
19
|
+
"hypa_mcp_proxy": "deny",
|
|
14
20
|
"write": "deny",
|
|
15
21
|
"edit": "deny",
|
|
16
22
|
"skill": {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitos-pizza/question — structured ask-user question tool.
|
|
3
|
+
* Supports single-question (legacy) and multi-question tabbed modes.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
import { startQuestionFileWatcher } from "../src/forwarding/file-watcher.ts";
|
|
8
9
|
import { startQuestionRpcServer } from "../src/forwarding/forwarder.ts";
|
|
10
|
+
import type { ForwardQuestionPayload } from "../src/forwarding/forwarder.ts";
|
|
9
11
|
import {
|
|
10
12
|
promptQuestionLocally,
|
|
11
13
|
registerQuestionTool,
|
|
12
14
|
} from "../src/register-tool.ts";
|
|
15
|
+
import type { MultiQuestionParams, QuestionParams } from "../src/types.ts";
|
|
13
16
|
|
|
14
17
|
export default function (
|
|
15
18
|
pi: import("@earendil-works/pi-coding-agent").ExtensionAPI,
|
|
@@ -18,24 +21,42 @@ export default function (
|
|
|
18
21
|
let stopFileWatcher: (() => void) | null = null;
|
|
19
22
|
const agentDir = getAgentDir();
|
|
20
23
|
|
|
21
|
-
const unsubRpc = startQuestionRpcServer(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
const unsubRpc = startQuestionRpcServer(
|
|
25
|
+
pi.events,
|
|
26
|
+
async (payload: ForwardQuestionPayload) => {
|
|
27
|
+
const ctx = activeCtx;
|
|
28
|
+
// No UI or wrong session — stay silent so callers can fall through
|
|
29
|
+
// to file-based forwarding (do not fake cancelled).
|
|
30
|
+
if (!ctx?.hasUI) return null;
|
|
31
|
+
|
|
32
|
+
const sessionId =
|
|
33
|
+
ctx.sessionManager.getSessionId?.() ?? "unknown";
|
|
34
|
+
if (
|
|
35
|
+
payload.targetSessionId &&
|
|
36
|
+
payload.targetSessionId !== sessionId
|
|
37
|
+
) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (payload.questions) {
|
|
42
|
+
return promptQuestionLocally(
|
|
43
|
+
ctx.ui,
|
|
44
|
+
{ questions: payload.questions } as MultiQuestionParams,
|
|
45
|
+
sessionId,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return promptQuestionLocally(
|
|
50
|
+
ctx.ui,
|
|
51
|
+
{
|
|
52
|
+
question: payload.question ?? "",
|
|
53
|
+
options: payload.options ?? [],
|
|
54
|
+
selectType: payload.selectType,
|
|
55
|
+
} as QuestionParams,
|
|
56
|
+
sessionId,
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
39
60
|
|
|
40
61
|
registerQuestionTool(pi, {
|
|
41
62
|
getAgentDir: () => agentDir,
|
|
@@ -54,16 +75,28 @@ export default function (
|
|
|
54
75
|
responderSessionId: sessionId,
|
|
55
76
|
showQuestion: async (params) => {
|
|
56
77
|
if (!activeCtx?.hasUI) {
|
|
78
|
+
if ("questions" in params) {
|
|
79
|
+
return {
|
|
80
|
+
answers: {},
|
|
81
|
+
cancelled: true,
|
|
82
|
+
responderSessionId: sessionId,
|
|
83
|
+
respondedAt: Date.now(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
57
86
|
return {
|
|
58
|
-
question: params.question,
|
|
59
|
-
options: params.options.map((o) => o.label),
|
|
87
|
+
question: (params as QuestionParams).question,
|
|
88
|
+
options: (params as QuestionParams).options.map((o) => o.label),
|
|
60
89
|
answer: null,
|
|
61
90
|
cancelled: true,
|
|
62
91
|
responderSessionId: sessionId,
|
|
63
92
|
respondedAt: Date.now(),
|
|
64
93
|
};
|
|
65
94
|
}
|
|
66
|
-
return promptQuestionLocally(
|
|
95
|
+
return promptQuestionLocally(
|
|
96
|
+
activeCtx.ui,
|
|
97
|
+
params,
|
|
98
|
+
sessionId,
|
|
99
|
+
);
|
|
67
100
|
},
|
|
68
101
|
});
|
|
69
102
|
}
|
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import type {
|
|
12
12
|
ForwardedQuestionRequest,
|
|
13
13
|
ForwardedQuestionResponse,
|
|
14
|
+
MultiQuestionParams,
|
|
14
15
|
QuestionParams,
|
|
15
16
|
} from "../types.ts";
|
|
16
17
|
import { getQuestionForwardingRoot } from "./forwarder.ts";
|
|
@@ -21,7 +22,9 @@ export function startQuestionFileWatcher(options: {
|
|
|
21
22
|
agentDir: string;
|
|
22
23
|
sessionId: string;
|
|
23
24
|
responderSessionId: string;
|
|
24
|
-
showQuestion: (
|
|
25
|
+
showQuestion: (
|
|
26
|
+
params: QuestionParams | MultiQuestionParams,
|
|
27
|
+
) => Promise<ForwardedQuestionResponse>;
|
|
25
28
|
pollIntervalMs?: number;
|
|
26
29
|
}): () => void {
|
|
27
30
|
const forwardingRoot = getQuestionForwardingRoot(
|
|
@@ -62,16 +65,43 @@ export function startQuestionFileWatcher(options: {
|
|
|
62
65
|
readFileSync(processingPath, "utf8"),
|
|
63
66
|
) as ForwardedQuestionRequest;
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
options
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
// Determine single vs multi
|
|
69
|
+
if (request.questions && request.questions.length > 0) {
|
|
70
|
+
const response = await options.showQuestion({
|
|
71
|
+
questions: request.questions,
|
|
72
|
+
});
|
|
73
|
+
writeFileSync(
|
|
74
|
+
join(responsesDir, `${requestId}.json`),
|
|
75
|
+
JSON.stringify(response),
|
|
76
|
+
"utf8",
|
|
77
|
+
);
|
|
78
|
+
} else if (request.question !== undefined && request.options) {
|
|
79
|
+
const response = await options.showQuestion({
|
|
80
|
+
question: request.question,
|
|
81
|
+
options: request.options,
|
|
82
|
+
selectType: request.selectType,
|
|
83
|
+
});
|
|
84
|
+
writeFileSync(
|
|
85
|
+
join(responsesDir, `${requestId}.json`),
|
|
86
|
+
JSON.stringify(response),
|
|
87
|
+
"utf8",
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
// Unknown format — write failure
|
|
91
|
+
const failure: ForwardedQuestionResponse = {
|
|
92
|
+
question: "",
|
|
93
|
+
options: [],
|
|
94
|
+
answer: null,
|
|
95
|
+
cancelled: true,
|
|
96
|
+
responderSessionId: options.responderSessionId,
|
|
97
|
+
respondedAt: Date.now(),
|
|
98
|
+
};
|
|
99
|
+
writeFileSync(
|
|
100
|
+
join(responsesDir, `${requestId}.json`),
|
|
101
|
+
JSON.stringify(failure),
|
|
102
|
+
"utf8",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
75
105
|
} catch {
|
|
76
106
|
const failure: ForwardedQuestionResponse = {
|
|
77
107
|
question: "",
|
|
@@ -7,11 +7,15 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import { isSubagentChild } from "../parent-session.ts";
|
|
10
11
|
import type {
|
|
11
12
|
ForwardedQuestionRequest,
|
|
12
13
|
ForwardedQuestionResponse,
|
|
14
|
+
MultiQuestionParams,
|
|
13
15
|
QuestionOption,
|
|
14
16
|
QuestionParams,
|
|
17
|
+
QuestionTab,
|
|
18
|
+
SelectType,
|
|
15
19
|
} from "../types.ts";
|
|
16
20
|
import { QUESTION_RPC_PROMPT, questionReplyChannel } from "./channels.ts";
|
|
17
21
|
|
|
@@ -20,6 +24,22 @@ interface EventBus {
|
|
|
20
24
|
on(channel: string, handler: (payload: unknown) => void): () => void;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Payload sent over RPC / file for a forwarded question prompt.
|
|
29
|
+
* Supports both single- and multi-question modes.
|
|
30
|
+
*/
|
|
31
|
+
export interface ForwardQuestionPayload {
|
|
32
|
+
requestId: string;
|
|
33
|
+
targetSessionId: string;
|
|
34
|
+
requesterSessionId: string;
|
|
35
|
+
/** Single‑question mode */
|
|
36
|
+
question?: string;
|
|
37
|
+
options?: QuestionOption[];
|
|
38
|
+
selectType?: SelectType;
|
|
39
|
+
/** Multi‑question mode */
|
|
40
|
+
questions?: QuestionTab[];
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
export function getQuestionForwardingRoot(
|
|
24
44
|
agentDir: string,
|
|
25
45
|
sessionId: string,
|
|
@@ -43,23 +63,37 @@ export async function forwardQuestionPrompt(options: {
|
|
|
43
63
|
agentDir?: string;
|
|
44
64
|
targetSessionId: string;
|
|
45
65
|
requesterSessionId: string;
|
|
46
|
-
params: QuestionParams;
|
|
66
|
+
params: QuestionParams | MultiQuestionParams;
|
|
47
67
|
timeoutMs?: number;
|
|
48
68
|
}): Promise<ForwardedQuestionResponse | null> {
|
|
49
69
|
const requestId = randomUUID();
|
|
50
|
-
|
|
70
|
+
|
|
71
|
+
const isMulti = "questions" in options.params;
|
|
72
|
+
const payload: ForwardQuestionPayload = {
|
|
51
73
|
requestId,
|
|
52
74
|
targetSessionId: options.targetSessionId,
|
|
53
75
|
requesterSessionId: options.requesterSessionId,
|
|
54
|
-
question: options.params.question,
|
|
55
|
-
options: options.params.options,
|
|
56
76
|
};
|
|
57
77
|
|
|
58
|
-
if (
|
|
78
|
+
if (isMulti) {
|
|
79
|
+
const mp = options.params as MultiQuestionParams;
|
|
80
|
+
payload.questions = mp.questions;
|
|
81
|
+
} else {
|
|
82
|
+
const sp = options.params as QuestionParams;
|
|
83
|
+
payload.question = sp.question;
|
|
84
|
+
payload.options = sp.options;
|
|
85
|
+
payload.selectType = sp.selectType;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Subprocess children have an isolated event bus; their own no-UI RPC
|
|
89
|
+
// server would poison-reply with cancelled. Skip RPC and use files only.
|
|
90
|
+
if (options.events && !isSubagentChild()) {
|
|
91
|
+
// Same-process RPC. If no capable listener replies, fall through to
|
|
92
|
+
// file-based forwarding quickly instead of waiting the full timeout.
|
|
59
93
|
const rpcReply = await waitForRpcReply(
|
|
60
94
|
options.events,
|
|
61
95
|
payload,
|
|
62
|
-
|
|
96
|
+
1500, // short RPC-specific timeout
|
|
63
97
|
);
|
|
64
98
|
if (rpcReply) return rpcReply;
|
|
65
99
|
}
|
|
@@ -74,7 +108,7 @@ export async function forwardQuestionPrompt(options: {
|
|
|
74
108
|
targetSessionId: options.targetSessionId,
|
|
75
109
|
params: options.params,
|
|
76
110
|
},
|
|
77
|
-
options.timeoutMs ??
|
|
111
|
+
options.timeoutMs ?? 300_000, // 5 min for user response
|
|
78
112
|
);
|
|
79
113
|
}
|
|
80
114
|
|
|
@@ -83,13 +117,7 @@ export async function forwardQuestionPrompt(options: {
|
|
|
83
117
|
|
|
84
118
|
function waitForRpcReply(
|
|
85
119
|
events: EventBus,
|
|
86
|
-
payload:
|
|
87
|
-
requestId: string;
|
|
88
|
-
targetSessionId: string;
|
|
89
|
-
requesterSessionId: string;
|
|
90
|
-
question: string;
|
|
91
|
-
options: QuestionOption[];
|
|
92
|
-
},
|
|
120
|
+
payload: ForwardQuestionPayload,
|
|
93
121
|
timeoutMs: number,
|
|
94
122
|
): Promise<ForwardedQuestionResponse | null> {
|
|
95
123
|
return new Promise((resolve) => {
|
|
@@ -121,20 +149,31 @@ function waitForFileReply(
|
|
|
121
149
|
requestId: string;
|
|
122
150
|
requesterSessionId: string;
|
|
123
151
|
targetSessionId: string;
|
|
124
|
-
params: QuestionParams;
|
|
152
|
+
params: QuestionParams | MultiQuestionParams;
|
|
125
153
|
},
|
|
126
154
|
timeoutMs: number,
|
|
127
155
|
): Promise<ForwardedQuestionResponse | null> {
|
|
128
156
|
const forwardingRoot = getQuestionForwardingRoot(agentDir, targetSessionId);
|
|
129
157
|
const { requestsDir, responsesDir } = getForwardingDirs(forwardingRoot);
|
|
158
|
+
|
|
159
|
+
const isMulti = "questions" in payload.params;
|
|
130
160
|
const request: ForwardedQuestionRequest = {
|
|
131
161
|
id: payload.requestId,
|
|
132
162
|
createdAt: Date.now(),
|
|
133
163
|
requesterSessionId: payload.requesterSessionId,
|
|
134
164
|
targetSessionId: payload.targetSessionId,
|
|
135
|
-
question: payload.params.question,
|
|
136
|
-
options: payload.params.options,
|
|
137
165
|
};
|
|
166
|
+
|
|
167
|
+
if (isMulti) {
|
|
168
|
+
const mp = payload.params as MultiQuestionParams;
|
|
169
|
+
request.questions = mp.questions;
|
|
170
|
+
} else {
|
|
171
|
+
const sp = payload.params as QuestionParams;
|
|
172
|
+
request.question = sp.question;
|
|
173
|
+
request.options = sp.options;
|
|
174
|
+
request.selectType = sp.selectType;
|
|
175
|
+
}
|
|
176
|
+
|
|
138
177
|
writeFileSync(
|
|
139
178
|
join(requestsDir, `${request.id}.json`),
|
|
140
179
|
JSON.stringify(request),
|
|
@@ -169,19 +208,14 @@ function waitForFileReply(
|
|
|
169
208
|
|
|
170
209
|
export function startQuestionRpcServer(
|
|
171
210
|
events: EventBus,
|
|
172
|
-
handler: (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
options: QuestionOption[];
|
|
176
|
-
}) => Promise<ForwardedQuestionResponse>,
|
|
211
|
+
handler: (
|
|
212
|
+
payload: ForwardQuestionPayload,
|
|
213
|
+
) => Promise<ForwardedQuestionResponse | null | undefined>,
|
|
177
214
|
): () => void {
|
|
178
215
|
return events.on(QUESTION_RPC_PROMPT, async (raw) => {
|
|
179
|
-
const payload = raw as
|
|
180
|
-
requestId: string;
|
|
181
|
-
question: string;
|
|
182
|
-
options: QuestionOption[];
|
|
183
|
-
};
|
|
216
|
+
const payload = raw as ForwardQuestionPayload;
|
|
184
217
|
const response = await handler(payload);
|
|
218
|
+
if (response == null) return;
|
|
185
219
|
events.emit(questionReplyChannel(payload.requestId), response);
|
|
186
220
|
});
|
|
187
221
|
}
|