palmier 1.1.7 → 1.1.8

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 CHANGED
@@ -56,8 +56,7 @@ Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://loca
56
56
  |------|-------------|
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
- | `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) |
59
+ | `request-choice` | Ask the user a question with a fixed set of options and return the one picked; a single option serves as a confirmation (blocks until response; aborting aborts the task) |
61
60
  | `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) |
62
61
  | `device-geolocation` | Get GPS location of the user's mobile device |
63
62
  | `read-contacts` | Read the contact list from the user's device |
@@ -19,6 +19,7 @@ export interface ToolDefinition {
19
19
  inputSchema: object;
20
20
  handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
21
21
  }
22
+ export declare const requestConfirmationTool: ToolDefinition;
22
23
  export declare const agentTools: ToolDefinition[];
23
24
  export declare const agentToolMap: Map<string, ToolDefinition>;
24
25
  export interface ResourceDefinition {
package/dist/mcp-tools.js CHANGED
@@ -145,7 +145,7 @@ async function abortSessionTask(ctx) {
145
145
  return;
146
146
  await ctx.abortTask(taskId);
147
147
  }
148
- const requestConfirmationTool = {
148
+ export const requestConfirmationTool = {
149
149
  name: "request-confirmation",
150
150
  description: [
151
151
  "Request confirmation from the user.",
@@ -191,10 +191,11 @@ const requestConfirmationTool = {
191
191
  const requestChoiceTool = {
192
192
  name: "request-choice",
193
193
  description: [
194
- "Ask the user a question with a fixed set of answers and let them pick one (e.g. Yes/No).",
194
+ "Ask the user a question with a fixed set of answers and let them pick one.",
195
195
  "The request blocks until the user picks an option or aborts the task.",
196
196
  'Response: `{"choice": "<option>"}` with the selected option, or `{"aborted": true}` if the user aborts.',
197
197
  "Use this instead of request-input whenever the possible answers are known in advance.",
198
+ "An abort option that ends the task is always shown, so to ask for confirmation before continuing, pass a single option naming the action (e.g. [\"Send email\"]). Add a decline option only if declining should let the task continue differently.",
198
199
  ],
199
200
  inputSchema: {
200
201
  type: "object",
@@ -203,8 +204,8 @@ const requestChoiceTool = {
203
204
  options: {
204
205
  type: "array",
205
206
  items: { type: "string" },
206
- description: 'Unique option labels, in display order (e.g. ["Yes", "No"])',
207
- minItems: 2,
207
+ description: 'Unique option labels, in display order (e.g. ["Reply now", "Reply later"])',
208
+ minItems: 1,
208
209
  },
209
210
  },
210
211
  required: ["question", "options"],
@@ -213,8 +214,8 @@ const requestChoiceTool = {
213
214
  const { question, options } = args;
214
215
  if (!question)
215
216
  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);
217
+ if (!Array.isArray(options) || options.length < 1 || options.some((o) => typeof o !== "string" || !o.trim())) {
218
+ throw new ToolError("options must contain at least 1 non-empty string", 400);
218
219
  }
219
220
  if (new Set(options).size !== options.length)
220
221
  throw new ToolError("options must be unique", 400);
@@ -820,7 +821,7 @@ const sendEmailTool = {
820
821
  return result;
821
822
  },
822
823
  };
823
- export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, requestChoiceTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
824
+ export const agentTools = [notifyTool, requestInputTool, requestChoiceTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
824
825
  export const agentToolMap = new Map(agentTools.map((t) => [t.name, t]));
825
826
  const deviceNotificationsResource = {
826
827
  uri: "notifications://device",
@@ -7,7 +7,7 @@ import { registerPending } from "../pending-requests.js";
7
7
  import * as fs from "node:fs";
8
8
  import { saveConfig } from "../config.js";
9
9
  import { detectDefaultInterface } from "../network.js";
10
- import { agentToolMap, agentResources, ToolError } from "../mcp-tools.js";
10
+ import { agentToolMap, agentResources, requestConfirmationTool, ToolError } from "../mcp-tools.js";
11
11
  import { handleMcpRequest, getAgentName, getResourceSubscriptions } from "../mcp-handler.js";
12
12
  import { getTaskDir } from "../task.js";
13
13
  import { popEvent } from "../event-queues.js";
@@ -189,12 +189,13 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
189
189
  }
190
190
  return;
191
191
  }
192
- if (req.method === "POST" && agentToolMap.has(pathname.slice(1))) {
192
+ const tool = agentToolMap.get(pathname.slice(1))
193
+ ?? (pathname === `/${requestConfirmationTool.name}` ? requestConfirmationTool : undefined);
194
+ if (req.method === "POST" && tool) {
193
195
  if (!isLocalhost(req)) {
194
196
  sendJson(res, 403, { error: "localhost only" });
195
197
  return;
196
198
  }
197
- const tool = agentToolMap.get(pathname.slice(1));
198
199
  try {
199
200
  const taskId = url.searchParams.get("taskId");
200
201
  if (!taskId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",