granttap-mcp 0.8.36 → 0.8.38

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
@@ -170,7 +170,8 @@ decided by the runtime after receipt verification.
170
170
 
171
171
  The first executable handoff path is Claude Code ↔ Codex across linked
172
172
  computers. GrantTap builds a bounded Task Capsule from explicit task/git facts,
173
- requires local phone authorization, creates a separate target branch/worktree,
173
+ requires authorization from the phone or the owning Mac's local desktop channel,
174
+ creates a separate target branch/worktree,
174
175
  starts the target execution, and returns a receipt bound to the exact capsule.
175
176
  Cursor uses the same provider-neutral schema and trusted caller attribution.
176
177
  Grok Build remains observable where available, but does not
@@ -192,6 +193,10 @@ the commit fetches that branch once before refusing. A handoff can also stay on
192
193
  one computer: the same Task continues with another agent there, in a worktree
193
194
  of its own from the same commit, taken up at once by the computer that
194
195
  prepared it.
196
+ The person can select an advertised destination model and add a bounded comment.
197
+ Both travel inside the encrypted capsule; the destination includes the comment
198
+ in the new execution's opening message and starts the selected model. A model
199
+ change on the same computer and provider is a new execution of the same Task.
195
200
 
196
201
  ### Pause and resume
197
202
 
@@ -70,6 +70,8 @@ export function buildTaskCapsule(
70
70
  targetProvider: request.targetProvider,
71
71
  targetActorId: request.targetActorId,
72
72
  targetComputer: request.targetComputer,
73
+ targetModel: request.targetModel,
74
+ userComment: request.userComment,
73
75
  repository: project.canonicalRepositoryId,
74
76
  baseSha: head,
75
77
  branch: session.branch ?? git(cwd, ["branch", "--show-current"]),
@@ -22,7 +22,7 @@ export type MeshRuntimeDependencies = {
22
22
  now: () => number;
23
23
  eventId: () => string;
24
24
  providerEnabled: (provider: Exclude<MeshProvider, "grok_bot">) => boolean;
25
- start: (provider: MeshProvider, prompt: string, cwd: string) => Promise<StartResult>;
25
+ start: (provider: MeshProvider, prompt: string, cwd: string, model?: string) => Promise<StartResult>;
26
26
  deliver: typeof deliverToSession;
27
27
  send: (
28
28
  client: RelayClient,
@@ -19,6 +19,7 @@ function capsulePrompt(capsule: TaskCapsule): string {
19
19
  `Base SHA: ${capsule.baseSha}`,
20
20
  ];
21
21
  if (capsule.latestCommit) lines.push(`Latest commit: ${capsule.latestCommit}`);
22
+ if (capsule.userComment) lines.push(`User comment: ${capsule.userComment}`);
22
23
  if (capsule.checkpoint) {
23
24
  const { status, files, excluded } = capsule.checkpoint;
24
25
  const line = status === "complete"
@@ -138,7 +139,9 @@ async function acceptHandoff(
138
139
  revision,
139
140
  );
140
141
  if (!worktree) return rejectHandoff(deps, client, event, "A separate handoff worktree could not be created.");
141
- const result = await deps.start(capsule.targetProvider, capsulePrompt(capsule), worktree.path);
142
+ const result = await deps.start(
143
+ capsule.targetProvider, capsulePrompt(capsule), worktree.path, capsule.targetModel,
144
+ );
142
145
  if (!result.ok || !result.sessionId) {
143
146
  const reason = result.ok ? "The target agent did not create a session." : result.error;
144
147
  return rejectHandoff(deps, client, event, reason);
@@ -62,11 +62,11 @@ const defaultDependencies: MeshRuntimeDependencies = {
62
62
  now: Date.now,
63
63
  eventId: randomUUID,
64
64
  providerEnabled: isProviderEnabled,
65
- start: async (provider, prompt, cwd) => {
66
- if (provider === "claude") return createClaudeSession(prompt, cwd);
67
- if (provider === "codex") return createCodexSession(prompt, cwd);
68
- if (provider === "cursor") return createCursorSession(prompt, cwd);
69
- return createGrokSession(prompt, cwd);
65
+ start: async (provider, prompt, cwd, model) => {
66
+ if (provider === "claude") return createClaudeSession(prompt, cwd, 240_000, [], model);
67
+ if (provider === "codex") return createCodexSession(prompt, cwd, 240_000, [], model);
68
+ if (provider === "cursor") return createCursorSession(prompt, cwd, 240_000, [], model);
69
+ return createGrokSession(prompt, cwd, 240_000, [], model);
70
70
  },
71
71
  deliver: deliverToSession,
72
72
  hasCommit: repositoryHasCommit,
@@ -37,7 +37,7 @@ details{border-top:1px solid #8884;padding-top:14px}summary{cursor:pointer;font-
37
37
  <dt>Last encrypted message from iPhone</dt><dd id="phone">Not observed</dd></dl>
38
38
  <h3>Provider readiness</h3><ul id="providers"></ul>
39
39
  <p>QR pairs a phone directly. A passkey links this Mac to your GrantTap account so your signed-in phone can discover its computers. Provider sign-in is separate.</p>
40
- <p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.36</code> and <code>granttap setup</code> locally.
40
+ <p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.38</code> and <code>granttap setup</code> locally.
41
41
  A saved pairing alone does not prove your phone or the background helper is online.</p>
42
42
  <p><a href="https://granttap.com" target="_blank" rel="noopener noreferrer">Website</a> ·
43
43
  <a href="https://granttap.com/privacy" target="_blank" rel="noopener noreferrer">Privacy</a> ·
@@ -10,7 +10,13 @@ same limits as encrypted phone messages. Invalid batches are rejected rather
10
10
  than silently losing files. The existing provider attachment pipeline stages
11
11
  images and arbitrary documents; the native client removes its batch after reply.
12
12
 
13
- Tests: `tests/desktop/desktop-task-send.test.ts`, local `tests/attachments.test.ts`.
13
+ `desktop.task_handoff` accepts a bounded handoff request from the authenticated
14
+ Mac desktop channel. It verifies the exact owning local execution, then asks the
15
+ canonical Mesh runtime to prepare the encrypted Task Capsule. A destination
16
+ model and user comment are carried with the Task, including for another computer.
17
+
18
+ Tests: `tests/desktop/desktop-task-send.test.ts`,
19
+ `tests/desktop/delivery/task-handoff.test.ts`, local `tests/attachments.test.ts`.
14
20
  Parent: [Desktop local channel](../README.md).
15
21
 
16
22
  This MCP component is covered by the repository [MIT License](../../../../../LICENSE).
@@ -0,0 +1,57 @@
1
+ import { join } from "node:path";
2
+ import { RelayClient } from "../../../../../packages/core/relay-client";
3
+ import { MeshHandoffPrepare, type MeshHandoffPrepare as HandoffRequest } from "../../../../../packages/protocol/schema";
4
+ import { configDir, loadConfig, machineConfigPath } from "../../../../bridge/src/config";
5
+ import { applyNetworkRoute } from "../../../../bridge/src/device-network/settings";
6
+ import { observedComputerId } from "../../../../bridge/src/mesh/identity/computer";
7
+ import { prepareMeshHandoff } from "../../../../bridge/src/mesh/runtime";
8
+ import { readStoreState } from "../../../../bridge/src/mesh/store/state";
9
+ import { controllerPeerGate } from "../../../../bridge/src/pairing/controllers";
10
+
11
+ type Prepare = (request: HandoffRequest) => Promise<boolean>;
12
+
13
+ async function prepareThroughRelay(request: HandoffRequest): Promise<boolean> {
14
+ const config = applyNetworkRoute(loadConfig(machineConfigPath()));
15
+ const client = new RelayClient(config, { peerAllowed: controllerPeerGate(config) });
16
+ try {
17
+ await client.connect();
18
+ return await prepareMeshHandoff(client, request);
19
+ } finally {
20
+ client.close();
21
+ }
22
+ }
23
+
24
+ /** Same-user Mac request, limited to the exact Task owner on this computer. */
25
+ export async function desktopTaskHandoff(
26
+ input: unknown,
27
+ storePath = join(configDir(), "project-mesh.json"),
28
+ prepare: Prepare = prepareThroughRelay,
29
+ localComputer = observedComputerId(),
30
+ ): Promise<unknown> {
31
+ if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
32
+ const row = input as Record<string, unknown>;
33
+ const request = MeshHandoffPrepare.safeParse({
34
+ type: "mesh.handoff.prepare",
35
+ sessionId: row.session_id, projectId: row.project_id, taskId: row.task_id,
36
+ targetProvider: row.target_provider, targetComputer: row.target_computer,
37
+ targetModel: row.target_model, userComment: row.user_comment,
38
+ checkpoint: row.checkpoint, push: row.push, createdAt: Date.now(),
39
+ });
40
+ if (!request.success) return undefined;
41
+ const loaded = readStoreState(storePath);
42
+ if (loaded.status !== "ok") return undefined;
43
+ const task = loaded.state.tasks.find((item) => item.taskId === request.data.taskId);
44
+ const execution = loaded.state.executions.find((item) =>
45
+ item.taskId === request.data.taskId && item.sessionId === request.data.sessionId
46
+ && item.endedAt == null && item.computerId === localComputer);
47
+ if (task?.projectId !== request.data.projectId || task.ownerSessionId !== request.data.sessionId
48
+ || !execution?.workspace) return undefined;
49
+ try {
50
+ const accepted = await prepare(request.data);
51
+ return { operation: "desktop.task_handoff", accepted,
52
+ error: accepted ? null : "The handoff was not ready. Check the Task and its repository." };
53
+ } catch {
54
+ return { operation: "desktop.task_handoff", accepted: false,
55
+ error: "The local relay or handoff service is unavailable." };
56
+ }
57
+ }
@@ -22,6 +22,7 @@ export const DESKTOP_OPERATIONS = new Set([
22
22
  "desktop.installed_skills", "desktop.policy_status", "desktop.policy_set", "desktop.project_auto_accept",
23
23
  "desktop.mesh_snapshots",
24
24
  "desktop.task_send",
25
+ "desktop.task_handoff",
25
26
  "desktop.task_create",
26
27
  "desktop.mesh_create",
27
28
  "desktop.invocation_history",
@@ -48,7 +49,7 @@ export function desktopOperationTimeout(operation: unknown, input: unknown): num
48
49
  || operation === "desktop.mesh_snapshots") return 75_000;
49
50
  if (operation === "desktop.task_activity"
50
51
  || operation === "desktop.task_image" || operation === "desktop.live_catalog") return 45_000;
51
- if (operation === "desktop.task_send"
52
+ if (operation === "desktop.task_send" || operation === "desktop.task_handoff"
52
53
  || operation === "desktop.task_create") return 250_000;
53
54
  if (operation === "desktop.mesh_snapshot"
54
55
  && (input as { enrich?: unknown } | undefined)?.enrich === "true") return 45_000;
@@ -2,8 +2,10 @@
2
2
 
3
3
  `routes.ts` exposes the existing private desktop bridge on loopback for the
4
4
  sandboxed Mac App Store client. This is a desktop-only authorization grant,
5
- separate from provider MCP OAuth. A local human consent page, same-origin POST,
6
- one-time code and S256 PKCE are required. The callback is fixed to
5
+ separate from provider MCP OAuth. Without a passkey account, a local human
6
+ consent page, a matching one-time consent cookie, one-time code and S256 PKCE
7
+ are required. An explicit foreign Origin is rejected; native browser sessions
8
+ may send an opaque Origin. The callback is fixed to
7
9
  `granttap://desktop-auth`; there is no user-selected redirect. Pending consent
8
10
  and codes expire after five minutes. A private file stores only token hashes,
9
11
  up to eight active grants, expiring after 30 days. The app keeps its token in
@@ -16,6 +18,11 @@ provider broader MCP scope. The installer/runtime remains outside the sandbox.
16
18
  `/desktop/account/link` uses the same native grant, verifies the app's fresh
17
19
  passkey account session with granttap.com, and stores a revocable machine
18
20
  credential locally. It returns only the account and machine identifiers.
21
+ `/desktop/account/authorize` exchanges the Mac app's passkey account session
22
+ for the same scoped local grant. The relay verifies the account and existing
23
+ machine identity, or enrolls this Mac if it has not been linked. The account
24
+ session remains in the app Keychain; cross-origin browser requests cannot read
25
+ it or submit JSON to the loopback service.
19
26
  Tests exercise real HTTP and Unix sockets, consent forgery rejection, PKCE,
20
27
  one-use codes, expiration, restart and authenticated forwarding.
21
28
 
@@ -44,6 +44,15 @@ export class DesktopNativeAccess {
44
44
  this.codes.delete(code);
45
45
  if (!row || row.expires <= this.now() || !/^[A-Za-z0-9_-]{43,128}$/.test(verifier)
46
46
  || digest(verifier) !== row.challenge) throw new Error('Invalid native code');
47
+ return this.issueGrant();
48
+ }
49
+
50
+ /** Called only after the local route verifies a passkey account with the relay. */
51
+ issueVerifiedAccountGrant(): string {
52
+ return this.issueGrant();
53
+ }
54
+
55
+ private issueGrant(): string {
47
56
  const token = random();
48
57
  const grants = this.grants().filter(grant => grant.expires > this.now()).slice(-7);
49
58
  grants.push({ digest: digest(token), expires: this.now() + 30 * 24 * 3_600_000 });
@@ -7,7 +7,7 @@ import { enrollMacAccount } from '../../../../bridge/src/account-recovery/enroll
7
7
  import { websiteOrigin } from '../../oauth/consent/website-session';
8
8
 
9
9
  export function installDesktopNativeRoutes(app: Express, socketPath: string, origin: string,
10
- access = new DesktopNativeAccess()): void {
10
+ access = new DesktopNativeAccess(), enroll = enrollMacAccount): void {
11
11
  app.use('/desktop', (_req, res, next) => {
12
12
  res.set({ 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer',
13
13
  'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'none'; form-action 'self'; frame-ancestors 'none'" });
@@ -26,7 +26,9 @@ export function installDesktopNativeRoutes(app: Express, socketPath: string, ori
26
26
  });
27
27
  app.post('/desktop/approve', (req, res) => {
28
28
  const confirmation = String(req.body?.confirmation ?? '');
29
- if (req.headers.origin !== origin || !confirmation ||
29
+ const requestOrigin = req.headers.origin;
30
+ if ((requestOrigin !== undefined && requestOrigin !== 'null' && requestOrigin !== origin)
31
+ || !confirmation ||
30
32
  !(req.headers.cookie ?? '').split(';').some(cookie => cookie.trim() === `granttap_desktop_consent=${confirmation}`)) {
31
33
  res.status(403).json({ error: 'Native approval requires this local consent page' }); return;
32
34
  }
@@ -53,6 +55,14 @@ export function installDesktopNativeRoutes(app: Express, socketPath: string, ori
53
55
  res.status(403).json({ error: 'Account link could not be verified' });
54
56
  }
55
57
  });
58
+ app.post('/desktop/account/authorize', express.json({ limit: '2kb' }), async (req, res) => {
59
+ try {
60
+ await enroll(String(req.body?.accountToken ?? ''), websiteOrigin() ?? 'https://granttap.com');
61
+ res.json({ access_token: access.issueVerifiedAccountGrant() });
62
+ } catch {
63
+ res.status(403).json({ error: 'Passkey account could not authorize this Mac' });
64
+ }
65
+ });
56
66
  app.post('/desktop/invoke', (req, res, next) => {
57
67
  const auth = String(req.headers.authorization ?? '');
58
68
  if (!auth.startsWith('Bearer ') || !access.verify(auth.slice(7))) {
@@ -10,6 +10,7 @@ import type { DesktopTaskActivityRunner } from "../task-activity-runner";
10
10
  import type { EngineClient } from "../../../../bridge/src/engine/runtime/engine-client";
11
11
  import type { EngineOperation } from "../../../../bridge/src/engine/protocol/engine-protocol";
12
12
  import { desktopTaskSend } from "../delivery/task-send";
13
+ import { desktopTaskHandoff } from "../delivery/task-handoff";
13
14
  import { desktopTaskCreate } from "../delivery/task-create";
14
15
  import { desktopMeshCreate } from "../delivery/mesh-create";
15
16
  import { desktopInvocationHistory } from "../invocation";
@@ -54,6 +55,7 @@ export function desktopReadOperation(input: {
54
55
  if (operation === "desktop.task_activity") return activity.read(queryInput, storePath);
55
56
  if (operation === "desktop.task_image") return activity.image(queryInput, storePath);
56
57
  if (operation === "desktop.task_send") return desktopTaskSend(queryInput, storePath);
58
+ if (operation === "desktop.task_handoff") return desktopTaskHandoff(queryInput, storePath);
57
59
  if (operation === "desktop.task_create") return desktopTaskCreate(queryInput, storePath);
58
60
  if (operation === "desktop.mesh_create") return Promise.resolve(desktopMeshCreate(queryInput, storePath));
59
61
  if (operation === "desktop.invocation_history") {
@@ -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.36
13
+ npm install -g granttap-mcp@0.8.38
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.36\";\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 20+ 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.38\";\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 20+ 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.36";
10
+ const PACKAGE = "granttap-mcp@0.8.38";
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.36",
3
+ "version": "0.8.38",
4
4
  "description": "Personal live control center runtime for local coding agents on iPhone and Apple Watch.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  "prepublishOnly": "npm run inventory && npm run structure -- --strict && npm run package:allowlist && npm run test:coverage && npm run typecheck"
34
34
  },
35
35
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "^1.30.0",
36
+ "@modelcontextprotocol/sdk": "^1.32.1",
37
37
  "express": "^5.2.1",
38
38
  "qrcode": "^1.5.4",
39
39
  "tsx": "^4.23.4",
@@ -18,6 +18,8 @@ export const TaskCapsule = z.object({
18
18
  targetProvider: MeshExecutionProvider,
19
19
  targetActorId: Identifier.optional(),
20
20
  targetComputer: Identifier,
21
+ targetModel: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/).optional(),
22
+ userComment: Detail.optional(),
21
23
  repository: z.string().trim().min(1).max(1_024),
22
24
  baseSha: GitSha,
23
25
  branch: z.string().trim().min(1).max(512).optional(),
@@ -206,6 +208,8 @@ export const MeshHandoffPrepare = z.object({
206
208
  targetProvider: MeshExecutionProvider,
207
209
  targetActorId: Identifier.optional(),
208
210
  targetComputer: Identifier,
211
+ targetModel: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/).optional(),
212
+ userComment: Detail.optional(),
209
213
  createdAt: z.number().nonnegative(),
210
214
  /**
211
215
  * Commit uncommitted work to a checkpoint branch first, so the Task can