@vitos-pizza/vitos-pizza 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -11
- package/README.md +1 -1
- package/package.json +12 -12
- package/packages/agent-mode/package.json +3 -3
- package/packages/agent-mode/src/plan-instructions.ts +14 -7
- package/packages/hypa/package.json +1 -1
- package/packages/keybindings/package.json +1 -1
- package/packages/permission-system/package.json +2 -2
- 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 -3
- package/packages/subagents/agents/scout.md +2 -2
- package/packages/subagents/agents/worker.md +2 -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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to vitos-pizza are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-07-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Multi-select questions** — `question` tool `selectType: "multi"` (Space toggle, Enter submit)
|
|
12
|
+
- **Multi-question tabs** — `questions: [...]` for tabbed prompts (Tab/←→ switch; per-tab single or multi)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Subagent hypa-only reads** — scout / planner / worker drop builtin `read`/`grep`/`find`/`ls`; exploration uses `hypa_*` only (`bash`/`edit`/`write` unchanged)
|
|
17
|
+
- Plan mode footer: **Worth considering** (gaps/adjacent capabilities) replaces **Next** (no mode-switch verbs)
|
|
18
|
+
- Subagent question forwarding: subprocess children use **file channel only** (skip in-process RPC)
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Subagent question false cancel** — child no-UI RPC no longer replies `cancelled`; parent TUI receives forwarded prompts
|
|
23
|
+
- **Resize recovery** — after Windows/Cursor reports `columns`/`rows` as 0 (or the border TUI is not ready yet), retry invalidate+full redraw for up to 1s so the UI recovers when size settles without another resize event
|
|
24
|
+
|
|
7
25
|
## [0.4.0] - 2026-07-11
|
|
8
26
|
|
|
9
27
|
### Added
|
|
@@ -50,7 +68,7 @@ All notable changes to vitos-pizza are documented here.
|
|
|
50
68
|
|
|
51
69
|
- **`@vitos-pizza/todoist`** — in-memory task list with TUI widget, `/todo` command, and LLM tools (`todo_add` / `todo_update` / `todo_complete` / `todo_delete`)
|
|
52
70
|
- Terminal **resize recovery** in ui-enhancements — debounce + full redraw after resize (fixes blank/corrupt TUI on Windows)
|
|
53
|
-
- Plan-mode **
|
|
71
|
+
- Plan-mode **Worth considering** footer (gaps/adjacent capabilities; not next-steps) and optional scout (skip recon when context is enough; parallel scouts for independent areas)
|
|
54
72
|
|
|
55
73
|
### Changed
|
|
56
74
|
|
|
@@ -70,15 +88,13 @@ All notable changes to vitos-pizza are documented here.
|
|
|
70
88
|
- Pi distribution layout: repo root as single install entry
|
|
71
89
|
- `scripts/sync-pi-manifest.mjs` module assembler
|
|
72
90
|
- Original logo at `assets/logo.svg`
|
|
91
|
+
- Built-in modules: agent-mode, question, session-title, permission-system, subagents, keybindings, ui-enhancements, websearch
|
|
92
|
+
- Agent modes: `agent` / `plan` / `execute` with `/mode` and cycle shortcuts
|
|
93
|
+
- Structured `question` tool with subagent parent-session forwarding
|
|
94
|
+
- Session auto-title from first meaningful user message
|
|
73
95
|
|
|
74
|
-
###
|
|
75
|
-
|
|
76
|
-
- Nested `packages/xin-pi` aggregator
|
|
77
|
-
- `@xin-pi/core`, template scaffold, postinstall symlink hack
|
|
78
|
-
|
|
79
|
-
### Publishing
|
|
96
|
+
### Acknowledgments
|
|
80
97
|
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
- First package version must be published once manually (or as a stub) before Trusted Publisher can be configured on npmjs.com
|
|
98
|
+
- [Pi](https://pi.dev/) / [@earendil-works/pi-coding-agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
|
|
99
|
+
- [@gotgenes/pi-permission-system](https://www.npmjs.com/package/@gotgenes/pi-permission-system)
|
|
100
|
+
- Pi official `question` and subagent examples
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Inspired by [Vito's Pizzeria](https://garfield.fandom.com/wiki/Vito_Cappelletti)
|
|
|
16
16
|
|
|
17
17
|

|
|
18
18
|
|
|
19
|
-
Typical flow: **plan** → optional **scout** → **planner** → confirm → **execute** / **worker**. Shell output is compressed via Hypa bash rewrite;
|
|
19
|
+
Typical flow: **plan** → optional **scout** → **planner** → confirm → **execute** / **worker**. Shell output is compressed via Hypa bash rewrite; scout/planner/worker explore with `hypa_*` only (main session may still use builtin reads).
|
|
20
20
|
|
|
21
21
|
| Capability | Module | What you get |
|
|
22
22
|
|------------|--------|--------------|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitos-pizza/vitos-pizza",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Vito's Pizzeria — a Pi distribution (Garfield-approved)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@hypabolic/pi-hypa": "^0.1.10",
|
|
41
41
|
"typebox": "^1.1.38",
|
|
42
|
-
"@vitos-pizza/agent-mode": "0.4.
|
|
43
|
-
"@vitos-pizza/hypa": "0.4.
|
|
44
|
-
"@vitos-pizza/keybindings": "0.4.
|
|
45
|
-
"@vitos-pizza/permission-system": "0.4.
|
|
46
|
-
"@vitos-pizza/question": "0.4.
|
|
47
|
-
"@vitos-pizza/session-title": "0.4.
|
|
48
|
-
"@vitos-pizza/settings-preset": "0.4.
|
|
49
|
-
"@vitos-pizza/subagents": "0.4.
|
|
50
|
-
"@vitos-pizza/todoist": "0.4.
|
|
51
|
-
"@vitos-pizza/ui-enhancements": "0.4.
|
|
52
|
-
"@vitos-pizza/websearch": "0.4.
|
|
42
|
+
"@vitos-pizza/agent-mode": "0.4.1",
|
|
43
|
+
"@vitos-pizza/hypa": "0.4.1",
|
|
44
|
+
"@vitos-pizza/keybindings": "0.4.1",
|
|
45
|
+
"@vitos-pizza/permission-system": "0.4.1",
|
|
46
|
+
"@vitos-pizza/question": "0.4.1",
|
|
47
|
+
"@vitos-pizza/session-title": "0.4.1",
|
|
48
|
+
"@vitos-pizza/settings-preset": "0.4.1",
|
|
49
|
+
"@vitos-pizza/subagents": "0.4.1",
|
|
50
|
+
"@vitos-pizza/todoist": "0.4.1",
|
|
51
|
+
"@vitos-pizza/ui-enhancements": "0.4.1",
|
|
52
|
+
"@vitos-pizza/websearch": "0.4.1"
|
|
53
53
|
},
|
|
54
54
|
"bundledDependencies": [
|
|
55
55
|
"@hypabolic/pi-hypa",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitos-pizza/agent-mode",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Centralized agent / plan / execute mode switching for Vito's Pizzeria",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"lint:fix": "biome check --write ."
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vitos-pizza/keybindings": "0.4.
|
|
17
|
-
"@vitos-pizza/permission-system": "0.4.
|
|
16
|
+
"@vitos-pizza/keybindings": "0.4.1",
|
|
17
|
+
"@vitos-pizza/permission-system": "0.4.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@earendil-works/pi-coding-agent": "*"
|
|
@@ -28,26 +28,33 @@ This supersedes conflicting skill or prior guidance about implementing code or c
|
|
|
28
28
|
|
|
29
29
|
**Exploration**
|
|
30
30
|
- Scout is optional — only for unfamiliar or multi-file recon. Parallel scouts via \`subagent({ tasks: [...] })\` for independent areas, then planner.
|
|
31
|
-
-
|
|
31
|
+
- Scout/planner use \`hypa_read\` / \`hypa_grep\` / \`hypa_find\` / \`hypa_ls\` only. Main session: prefer hypa_*; fall back to read/grep/find/ls if needed.
|
|
32
32
|
- Web tools only for current external facts outside the repo.
|
|
33
33
|
|
|
34
34
|
**Delegation**
|
|
35
35
|
- Enough context → \`subagent({ agent: "planner", task })\`
|
|
36
36
|
- Need recon → \`subagent({ chain: [scout, planner] })\`
|
|
37
37
|
|
|
38
|
-
**Optional
|
|
38
|
+
**Optional open-questions footer** (2–4 one-line prompts) only when the plan may miss scope or adjacent capabilities the user has not clarified. Soft guidance — not next-steps, not mode-switching:
|
|
39
39
|
|
|
40
40
|
\`\`\`
|
|
41
|
-
**
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
41
|
+
**Worth considering**
|
|
42
|
+
- <gap or adjacent capability the user may not have decided yet>?
|
|
43
|
+
- <in/out of scope this round>?
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
Rules:
|
|
47
|
+
- Ask about omissions, edge cases, related features, or "do you also need X?"
|
|
48
|
+
- Do NOT list mode-switching or how-to-proceed verbs (e.g. \`/mode execute\`, Ctrl+.)
|
|
49
|
+
- Do NOT restate the plan as todos
|
|
50
|
+
- Prefer \`question\` for mutually exclusive choices; use this footer for soft, optional prompts
|
|
51
|
+
- Skip the footer when scope is already clear`;
|
|
45
52
|
|
|
46
53
|
export const PLAN_MODE_MESSAGE = `[PLAN MODE ACTIVE]
|
|
47
54
|
|
|
48
55
|
Read-only planning. No write/edit/bash/worker.
|
|
49
56
|
Clarify with \`question\` when needed. Scout only if recon is required; otherwise planner.
|
|
50
|
-
Prefer hypa_* read tools when available. Return the plan and wait; optionally a short **
|
|
57
|
+
Prefer hypa_* read tools when available. Return the plan and wait; optionally a short **Worth considering** footer for gaps/adjacent capabilities.`;
|
|
51
58
|
|
|
52
59
|
export const PLAN_MODE_ENDED_MESSAGE = `[PLAN MODE ENDED]
|
|
53
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitos-pizza/permission-system",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Permission enforcement for Vito's Pizzeria Pi distribution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"lint:fix": "biome check --write ."
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vitos-pizza/keybindings": "0.4.
|
|
21
|
+
"@vitos-pizza/keybindings": "0.4.1",
|
|
22
22
|
"tree-sitter-bash": "^0.25.1",
|
|
23
23
|
"web-tree-sitter": "^0.25.10",
|
|
24
24
|
"zod": "^3.25.76"
|
|
@@ -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
|
}
|