palmier 1.0.11 → 1.0.13
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/README.md +1 -0
- package/dist/mcp-tools.js +66 -6
- package/dist/pending-requests.d.ts +4 -4
- package/dist/pending-requests.js +1 -1
- package/dist/pwa/assets/index-BuSLgLCl.js +121 -0
- package/dist/pwa/assets/index-UIYx_N7v.css +1 -0
- package/dist/pwa/assets/{web-DRfjN6eG.js → web-B4ZYORQP.js} +1 -1
- package/dist/pwa/assets/{web-WpTCk2Nv.js → web-BIv7BTtP.js} +1 -1
- package/dist/pwa/assets/{web-zYbQ8b4R.js → web-DtlUNQHI.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.js +21 -3
- package/dist/task.d.ts +2 -0
- package/dist/task.js +12 -0
- package/package.json +1 -1
- package/dist/pwa/assets/index-CgjL0Axh.js +0 -121
- package/dist/pwa/assets/index-DMYhzfFT.css +0 -1
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://loca
|
|
|
57
57
|
| `notify` | Send a push notification to the user's device |
|
|
58
58
|
| `request-input` | Request input from the user (blocks until response; aborting the prompt aborts the task) |
|
|
59
59
|
| `request-confirmation` | Request confirmation from the user (blocks until response; aborting aborts the task) |
|
|
60
|
+
| `request-choice` | Ask the user a question with a fixed set of options, e.g. Yes/No, and return the one picked (blocks until response; aborting aborts the task) |
|
|
60
61
|
| `fill-password` | Fill a saved password or PIN into the active browser session and submit the form, prompting the user if unknown (the secret is never revealed to the agent) |
|
|
61
62
|
| `device-geolocation` | Get GPS location of the user's mobile device |
|
|
62
63
|
| `read-contacts` | Read the contact list from the user's device |
|
package/dist/mcp-tools.js
CHANGED
|
@@ -89,7 +89,9 @@ const requestInputTool = {
|
|
|
89
89
|
});
|
|
90
90
|
const response = await pendingPromise;
|
|
91
91
|
const aborted = response.length === 1 && response[0] === "aborted";
|
|
92
|
-
|
|
92
|
+
recordUserResponseToRun(ctx, aborted
|
|
93
|
+
? "Aborted"
|
|
94
|
+
: questions.map((q, i) => `**${q}**\n\n${response[i] ?? ""}`).join("\n\n"));
|
|
93
95
|
if (aborted) {
|
|
94
96
|
await ctx.publishEvent("_input", {
|
|
95
97
|
event_type: "input-resolved", host_id: ctx.config.hostId,
|
|
@@ -112,7 +114,7 @@ const requestInputTool = {
|
|
|
112
114
|
* agent called via the REST endpoint with taskId); silently skips for pure
|
|
113
115
|
* MCP-protocol sessions where sessionId is a random UUID.
|
|
114
116
|
*/
|
|
115
|
-
function
|
|
117
|
+
function recordUserResponseToRun(ctx, content) {
|
|
116
118
|
const taskId = ctx.sessionId;
|
|
117
119
|
if (!taskId)
|
|
118
120
|
return;
|
|
@@ -123,9 +125,6 @@ function recordUserInputToRun(ctx, questions, response, aborted) {
|
|
|
123
125
|
const runId = entries[0]?.run_id;
|
|
124
126
|
if (!runId)
|
|
125
127
|
return;
|
|
126
|
-
const content = aborted
|
|
127
|
-
? "Aborted"
|
|
128
|
-
: questions.map((q, i) => `**${q}**\n\n${response[i] ?? ""}`).join("\n\n");
|
|
129
128
|
try {
|
|
130
129
|
spliceUserMessage(taskDir, runId, {
|
|
131
130
|
role: "user",
|
|
@@ -189,6 +188,67 @@ const requestConfirmationTool = {
|
|
|
189
188
|
return { confirmed };
|
|
190
189
|
},
|
|
191
190
|
};
|
|
191
|
+
const requestChoiceTool = {
|
|
192
|
+
name: "request-choice",
|
|
193
|
+
description: [
|
|
194
|
+
"Ask the user a question with a fixed set of answers and let them pick one (e.g. Yes/No).",
|
|
195
|
+
"The request blocks until the user picks an option or aborts the task.",
|
|
196
|
+
'Response: `{"choice": "<option>"}` with the selected option, or `{"aborted": true}` if the user aborts.',
|
|
197
|
+
"Use this instead of request-input whenever the possible answers are known in advance.",
|
|
198
|
+
],
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
question: { type: "string", description: "The question to ask" },
|
|
203
|
+
options: {
|
|
204
|
+
type: "array",
|
|
205
|
+
items: { type: "string" },
|
|
206
|
+
description: 'Unique option labels, in display order (e.g. ["Yes", "No"])',
|
|
207
|
+
minItems: 2,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ["question", "options"],
|
|
211
|
+
},
|
|
212
|
+
async handler(args, ctx) {
|
|
213
|
+
const { question, options } = args;
|
|
214
|
+
if (!question)
|
|
215
|
+
throw new ToolError("question is required", 400);
|
|
216
|
+
if (!Array.isArray(options) || options.length < 2 || options.some((o) => typeof o !== "string" || !o.trim())) {
|
|
217
|
+
throw new ToolError("options must contain at least 2 non-empty strings", 400);
|
|
218
|
+
}
|
|
219
|
+
if (new Set(options).size !== options.length)
|
|
220
|
+
throw new ToolError("options must be unique", 400);
|
|
221
|
+
const pendingPromise = registerPending(ctx.sessionId, "choice", options, {
|
|
222
|
+
session_id: ctx.sessionId,
|
|
223
|
+
session_name: ctx.agentName,
|
|
224
|
+
description: question,
|
|
225
|
+
});
|
|
226
|
+
await ctx.publishEvent("_choice", {
|
|
227
|
+
event_type: "choice-request",
|
|
228
|
+
host_id: ctx.config.hostId,
|
|
229
|
+
session_id: ctx.sessionId,
|
|
230
|
+
session_name: ctx.agentName,
|
|
231
|
+
description: question,
|
|
232
|
+
options,
|
|
233
|
+
});
|
|
234
|
+
// Clients answer with the option index so no label can collide with the "aborted" sentinel.
|
|
235
|
+
const response = await pendingPromise;
|
|
236
|
+
const choice = /^\d+$/.test(response[0] ?? "") ? options[Number(response[0])] : undefined;
|
|
237
|
+
const aborted = choice === undefined;
|
|
238
|
+
recordUserResponseToRun(ctx, aborted ? "Aborted" : `**${question}**\n\n${choice}`);
|
|
239
|
+
await ctx.publishEvent("_choice", {
|
|
240
|
+
event_type: "choice-resolved",
|
|
241
|
+
host_id: ctx.config.hostId,
|
|
242
|
+
session_id: ctx.sessionId,
|
|
243
|
+
status: aborted ? "aborted" : "chosen",
|
|
244
|
+
});
|
|
245
|
+
if (aborted) {
|
|
246
|
+
await abortSessionTask(ctx);
|
|
247
|
+
return { aborted: true };
|
|
248
|
+
}
|
|
249
|
+
return { choice };
|
|
250
|
+
},
|
|
251
|
+
};
|
|
192
252
|
const fillPasswordTool = {
|
|
193
253
|
name: "fill-password",
|
|
194
254
|
description: [
|
|
@@ -760,7 +820,7 @@ const sendEmailTool = {
|
|
|
760
820
|
return result;
|
|
761
821
|
},
|
|
762
822
|
};
|
|
763
|
-
export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
|
|
823
|
+
export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, requestChoiceTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
|
|
764
824
|
export const agentToolMap = new Map(agentTools.map((t) => [t.name, t]));
|
|
765
825
|
const deviceNotificationsResource = {
|
|
766
826
|
uri: "notifications://device",
|
|
@@ -3,7 +3,7 @@ export interface PendingRequestMeta {
|
|
|
3
3
|
/** Doubles as task_id for permission-type entries (the key the task uses). */
|
|
4
4
|
session_id?: string;
|
|
5
5
|
/** Human-readable label for whoever opened the prompt — agent name for
|
|
6
|
-
* confirm/input, task name for permission. */
|
|
6
|
+
* confirm/input/choice, task name for permission. */
|
|
7
7
|
session_name?: string;
|
|
8
8
|
description?: string;
|
|
9
9
|
input_questions?: string[];
|
|
@@ -11,15 +11,15 @@ export interface PendingRequestMeta {
|
|
|
11
11
|
secret?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export interface PendingRequest {
|
|
14
|
-
type: "confirmation" | "permission" | "input";
|
|
14
|
+
type: "confirmation" | "permission" | "input" | "choice";
|
|
15
15
|
resolve: (value: string[]) => void;
|
|
16
|
-
/** Permission list (for 'permission')
|
|
16
|
+
/** Permission list (for 'permission'), input descriptions (for 'input'), or option labels (for 'choice'). */
|
|
17
17
|
params?: RequiredPermission[] | string[];
|
|
18
18
|
/** Display context for PWAs that connect while this request is already open. */
|
|
19
19
|
meta?: PendingRequestMeta;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Key is sessionId for confirmation/input, taskId for permission. Only one
|
|
22
|
+
* Key is sessionId for confirmation/input/choice, taskId for permission. Only one
|
|
23
23
|
* pending request per key at a time. `meta` is surfaced via host.info so a
|
|
24
24
|
* freshly-connected PWA can render the modal without replaying events.
|
|
25
25
|
*/
|
package/dist/pending-requests.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const pending = new Map();
|
|
2
2
|
/**
|
|
3
|
-
* Key is sessionId for confirmation/input, taskId for permission. Only one
|
|
3
|
+
* Key is sessionId for confirmation/input/choice, taskId for permission. Only one
|
|
4
4
|
* pending request per key at a time. `meta` is surfaced via host.info so a
|
|
5
5
|
* freshly-connected PWA can render the modal without replaying events.
|
|
6
6
|
*/
|