granttap-mcp 0.8.94 → 0.8.95

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.
@@ -25,6 +25,10 @@ provider. Rejected preparations also publish their retained Project state.
25
25
 
26
26
  Capsule status is bounded independently from the complete conversation capture;
27
27
  a long native summary cannot invalidate an otherwise transferable Git Task.
28
+ Local prepared handoffs persist their destination and validated receipt before
29
+ publishing to the phone. A blocked socket cannot hold the local preparation
30
+ behind a notification. Publication errors retain both durable events for the
31
+ next snapshot; they never replay the transfer or start a provider.
28
32
 
29
33
  Tests: `tests/metadata.test.ts`, `tests/source-checkout.test.ts`,
30
34
  `tests/mesh/mesh-handoff-safety.test.ts`, and catalog
@@ -0,0 +1,23 @@
1
+ import type { RelayClient } from '../../../../../../packages/core/relay-client';
2
+ import type { MeshEvent } from '../../../../../../packages/protocol/schema';
3
+ import type { MeshRuntimeDependencies } from '../dependencies';
4
+ import { createHandoffFlow } from '../handoff';
5
+ import { preparedHandoffMetadata } from '.';
6
+
7
+ /** Commit the local destination before phone publication can wait on a socket. */
8
+ export async function prepareLocalHandoff(deps: MeshRuntimeDependencies, client: RelayClient,
9
+ event: MeshEvent): Promise<boolean> {
10
+ const notifications: Parameters<MeshRuntimeDependencies['send']>[] = [];
11
+ const flow = createHandoffFlow({ ...deps, send: async (...notification) => { notifications.push(notification); } });
12
+ await flow.acceptHandoff(client, event);
13
+ const accepted = notifications.some(([, payload]) => payload.type === 'mesh.event'
14
+ && payload.eventType === 'HANDOFF_ACCEPTED');
15
+ const request: Parameters<MeshRuntimeDependencies['send']> = [client, preparedHandoffMetadata(event),
16
+ { ttlMs: 60 * 60_000, wake: true }];
17
+ for (const notification of [request, ...notifications]) {
18
+ // Both events are durable already. A transport error cannot undo the
19
+ // prepared chat or trigger a second preparation; retained snapshots recover it.
20
+ try { await deps.send(...notification); } catch { /* next connection publishes durable state */ }
21
+ }
22
+ return accepted;
23
+ }
@@ -6,6 +6,7 @@ import { prepareCloudHandoff } from "../cloud-handoff";
6
6
  import { capturePreparedHandoff, preparedHandoffMetadata } from ".";
7
7
  import { prepareSourceCapsule } from "./source";
8
8
  import { resolveHandoffSource } from "./source-binding";
9
+ import { prepareLocalHandoff } from "./local";
9
10
 
10
11
  export async function prepareHandoff(
11
12
  deps: MeshRuntimeDependencies,
@@ -86,6 +87,7 @@ export async function prepareHandoff(
86
87
  return false;
87
88
  }
88
89
  deps.store().acceptEvent(event);
90
+ if (local && request.prepareOnly) return prepareLocalHandoff(deps, client, event);
89
91
  await deps.send(client, request.prepareOnly ? preparedHandoffMetadata(event) : event, { ttlMs: 60 * 60_000, wake: true });
90
92
  if (local) await acceptHandoff(client, event);
91
93
  return true;
@@ -10,7 +10,7 @@ keeps each phone response tied to the exact originating chat and prompt.
10
10
  Install the supported CLI on this computer, then run:
11
11
 
12
12
  ```bash
13
- npm install -g granttap-mcp@0.8.94
13
+ npm install -g granttap-mcp@0.8.95
14
14
  granttap setup
15
15
  granttap status
16
16
  ```
@@ -4,7 +4,7 @@
4
4
  "command": "node",
5
5
  "args": [
6
6
  "-e",
7
- "\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.94\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 22.13.0+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
7
+ "\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.95\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 22.13.0+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
8
8
  ]
9
9
  }
10
10
  }
@@ -7,7 +7,7 @@ const { spawn } = require("node:child_process");
7
7
  const { existsSync } = require("node:fs");
8
8
  const { delimiter, dirname, join } = require("node:path");
9
9
 
10
- const PACKAGE = "granttap-mcp@0.8.94";
10
+ const PACKAGE = "granttap-mcp@0.8.95";
11
11
  const dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === "1";
12
12
  const platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM
13
13
  ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granttap-mcp",
3
- "version": "0.8.94",
3
+ "version": "0.8.95",
4
4
  "description": "Commercial computer runtime connecting local coding agents to GrantTap on Mac, iPhone, iPad and Apple Watch.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,8 @@
3
3
  Public entry point: `index.ts`. Preserves per-peer encrypted envelopes while
4
4
  pacing a connection below the managed relay's 300-frame-per-second admission
5
5
  limit. Explicit approval and configuration messages take priority over queued
6
- replaceable snapshots. TCP write completion, bounded queued bytes, cancellation
6
+ replaceable snapshots. Encrypted results explicitly marked for wake delivery
7
+ also precede replaceable snapshots, including sealed transfer receipts. TCP write completion, bounded queued bytes, cancellation
7
8
  on disconnect, and a fresh queue on reconnect prevent stale bursts from crossing
8
9
  connection generations. No message content or keys are logged.
9
10
 
@@ -83,6 +83,6 @@ export async function sendEncryptedPayload(queue: OutboundFrameQueue, config: Pe
83
83
  if (!peers.length) throw new Error("no authorized pairing peers");
84
84
  for (const peerPublicKey of peers) {
85
85
  await queue.send(sealEnvelope({ ...config, peerPublicKey }, payload, to,
86
- peers.length === 1 ? options : { ...options, deliveryId: undefined }), control(payload.type));
86
+ peers.length === 1 ? options : { ...options, deliveryId: undefined }), control(payload.type) || options.wake === true);
87
87
  }
88
88
  }