granttap-mcp 0.8.104 → 0.8.106
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/apps/bridge/src/codex-desktop/README.md +5 -0
- package/apps/bridge/src/codex-desktop/index.ts +2 -1
- package/apps/bridge/src/codex-desktop/question-attachment-envelope.ts +23 -0
- package/apps/bridge/src/codex-questions/README.md +7 -0
- package/apps/bridge/src/codex-questions/index.ts +4 -2
- package/apps/bridge/src/engine/README.md +2 -2
- package/apps/bridge/src/engine/distribution/artifacts.ts +2 -2
- package/apps/bridge/src/engine/runtime/engine-supervisor.ts +2 -3
- package/apps/bridge/src/engine/runtime/launch/README.md +18 -0
- package/apps/bridge/src/engine/runtime/launch/index.ts +21 -0
- package/cursor-plugin/README.md +1 -1
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +1 -1
- package/packages/protocol/messages/interaction/index.ts +4 -0
|
@@ -29,3 +29,8 @@ consumed them. Tests exercise native delivery, framing, socket trust, durable
|
|
|
29
29
|
deduplication, and refusal without launching the CLI.
|
|
30
30
|
|
|
31
31
|
License: GrantTap Commercial Source License in the repository-root `LICENSE`.
|
|
32
|
+
|
|
33
|
+
Document staging for a native-only question keeps the file note inside its
|
|
34
|
+
original answer envelope. The host's native question and all paired controllers
|
|
35
|
+
therefore observe the same question item as answered; document paths remain
|
|
36
|
+
available to the admitted turn. Ordinary chat text is unchanged.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nativeQuestionAttachmentEnvelope } from './question-attachment-envelope';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
3
4
|
import type { SessionInfo } from '../../../../packages/protocol/schema';
|
|
@@ -72,7 +73,7 @@ function turnRequest(input: { session: SessionInfo; text: string; images: string
|
|
|
72
73
|
networkAccess: false, excludeTmpdirEnvVar: false, excludeSlashTmp: false }
|
|
73
74
|
: access === 'full' ? { type: 'dangerFullAccess' } : undefined;
|
|
74
75
|
return { threadId: session.sessionId, clientUserMessageId: options.messageId ?? randomUUID(),
|
|
75
|
-
input: [{ type: 'text', text, text_elements: [] }, ...images.map(path => ({ type: 'localImage', path }))],
|
|
76
|
+
input: [{ type: 'text', text: options.nativeOnly ? nativeQuestionAttachmentEnvelope(text) : text, text_elements: [] }, ...images.map(path => ({ type: 'localImage', path }))],
|
|
76
77
|
...(options.model ? { model: options.model } : {}), ...(effort ? { effort } : {}),
|
|
77
78
|
...(sandboxPolicy ? { sandboxPolicy } : {}) };
|
|
78
79
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Preserve the native question envelope after document staging appends its local file note. */
|
|
2
|
+
export function nativeQuestionAttachmentEnvelope(text: string): string {
|
|
3
|
+
const opening = '<send_user_message_question_reply>';
|
|
4
|
+
const closing = '</send_user_message_question_reply>';
|
|
5
|
+
const boundary = text.lastIndexOf(closing);
|
|
6
|
+
if (!text.startsWith(opening) || boundary < opening.length) return text;
|
|
7
|
+
const note = text.slice(boundary + closing.length);
|
|
8
|
+
const header = '\n\nAttached files available locally:\n';
|
|
9
|
+
if (!note.startsWith(header)) return text;
|
|
10
|
+
const files = note.slice(header.length).split('\n');
|
|
11
|
+
if (!files.length || files.some(line => !/^- .+: .+$/.test(line))) return text;
|
|
12
|
+
try {
|
|
13
|
+
const rows = JSON.parse(text.slice(opening.length, boundary));
|
|
14
|
+
if (!Array.isArray(rows) || rows.length !== 1 || typeof rows[0]?.questionItemId !== 'string'
|
|
15
|
+
|| typeof rows[0]?.answer !== 'string') return text;
|
|
16
|
+
const identity = JSON.parse(rows[0].questionItemId);
|
|
17
|
+
if (!Array.isArray(identity) || identity.length !== 3 || identity[0] !== 'request_user_input_async'
|
|
18
|
+
|| typeof identity[1] !== 'string' || !/^[a-zA-Z0-9_-]{1,180}$/.test(identity[1])
|
|
19
|
+
|| !Number.isInteger(identity[2]) || identity[2] < 0 || identity[2] > 3) return text;
|
|
20
|
+
rows[0].answer += note;
|
|
21
|
+
return opening + '\n' + JSON.stringify(rows) + '\n' + closing;
|
|
22
|
+
} catch { return text; }
|
|
23
|
+
}
|
|
@@ -25,3 +25,10 @@ Tests under `tests/` cover publication, reconnect replay, native acknowledgement
|
|
|
25
25
|
scope rejection, expiry and delivery through an isolated native IPC fixture.
|
|
26
26
|
|
|
27
27
|
License: GrantTap Commercial Source License in the repository-root `LICENSE`.
|
|
28
|
+
|
|
29
|
+
Attachment-capable controllers opt in using the advertised
|
|
30
|
+
`questionReplyFormat: attachments-v1`. The original question item owns the
|
|
31
|
+
answer, image and file payload together; ordinary encrypted attachment transport,
|
|
32
|
+
Task policy, native-owner admission and stable question idempotency still apply.
|
|
33
|
+
Legacy text-only replies keep their existing contract. Files remain available
|
|
34
|
+
for the admitted native turn; another phone cannot start a second answer.
|
|
@@ -40,6 +40,7 @@ export async function publishCodexQuestions(client: RelayClient, sessions: Sessi
|
|
|
40
40
|
} else if (!record.announced || options.replay) {
|
|
41
41
|
const text = [record.question, ...record.options.map(value => '• ' + value)].join('\n');
|
|
42
42
|
await client.send({ type: 'agent.event', kind: 'question', requestId: record.requestId,
|
|
43
|
+
questionReplyFormat: 'attachments-v1',
|
|
43
44
|
sessionId: session.sessionId, agent: 'codex', projectId: session.projectId,
|
|
44
45
|
taskId: session.taskId, computerId: session.computerId, text, createdAt: record.createdAt },
|
|
45
46
|
'phone', { ttlMs: Math.max(1, record.createdAt + RETENTION_MS - Date.now()),
|
|
@@ -73,8 +74,9 @@ export async function handleCodexQuestionReply(client: RelayClient, message: Use
|
|
|
73
74
|
await resolveQuestion(client, record, peerPublicKey, !record.answered);
|
|
74
75
|
return true;
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
const hasAttachments = !!(message.attachments?.length || message.attachmentRefs?.length);
|
|
78
|
+
if (!message.text.trim() || message.text.length > 4_000
|
|
79
|
+
|| (hasAttachments && message.questionReplyFormat !== 'attachments-v1')) return false;
|
|
78
80
|
return deliver(client, { ...message, requestId: undefined,
|
|
79
81
|
messageId: message.messageId ?? 'codex-answer-' + record.requestId,
|
|
80
82
|
text: nativeAnswerEnvelope(record, message.text) }, publish, peerPublicKey,
|
|
@@ -29,7 +29,7 @@ Explicit Graph builds also invoke the Engine's `graph.analyze_project` for two
|
|
|
29
29
|
to eight admitted local repositories. The projection labels these shared static
|
|
30
30
|
contracts and keeps their runtime destination unverified. They do not become
|
|
31
31
|
verified Backbone dependencies or merge Project grants. The Apple Silicon
|
|
32
|
-
artifact pins Engine 0.1.
|
|
32
|
+
artifact pins Engine 0.1.4 with Weavatrix 2.17.5 and injective package import identities; unsupported Engine operations
|
|
33
33
|
leave this analysis unavailable. See `runtime/project-relationships/README.md`.
|
|
34
34
|
|
|
35
35
|
Claude, Codex, and Cursor hooks can evaluate the engine's local Project
|
|
@@ -62,7 +62,7 @@ on macOS, Linux and Windows rather than relying on a LaunchAgent environment.
|
|
|
62
62
|
Explicit disablement and a complete explicit executable/checksum pair win over
|
|
63
63
|
the saved declaration. Partial environment overrides are never mixed with a
|
|
64
64
|
different file's checksum. The supervisor still verifies the executable's bytes
|
|
65
|
-
before launch. Windows clients derive a local named pipe from the normalized
|
|
65
|
+
before launch. Its child process receives a minimum 32 MiB Rust worker stack: the default stack aborts the native parser on long, valid Rust syntax trees. Larger explicit reserves are retained. The parent process environment, Engine enablement and project policy settings are unchanged. This is a worker stack reserve, not a scan timeout or a guarantee that arbitrary inputs cannot exhaust resources. Windows clients derive a local named pipe from the normalized
|
|
66
66
|
configuration path; remote shares never become Engine endpoints.
|
|
67
67
|
The paired monitor installs the pinned managed Engine in the background when
|
|
68
68
|
needed. Catalog delivery does not await that download. Failed preparation retries
|
|
@@ -8,8 +8,8 @@ export type EngineArtifact = {
|
|
|
8
8
|
|
|
9
9
|
/** Release hashes are part of the reviewed MCP, never supplied by a remote manifest. */
|
|
10
10
|
export const ENGINE_ARTIFACTS: readonly EngineArtifact[] = [
|
|
11
|
-
{ version: "0.1.
|
|
12
|
-
sha256: "
|
|
11
|
+
{ version: "0.1.4", platform: "darwin", arch: "arm64",
|
|
12
|
+
sha256: "0eda3a158b84305802342cff77af7a7c24fecd5eafb25fad394e61edad53fbec", bytes: 12202064 },
|
|
13
13
|
{ version: "0.1.2", platform: "darwin", arch: "x64",
|
|
14
14
|
sha256: "b3235a15936a77ec323d5ecbc54a0bc19994fe6e5a36044da964b68524b4c1dd", bytes: 13944984 },
|
|
15
15
|
{ version: "0.1.2", platform: "linux", arch: "x64",
|
|
@@ -8,7 +8,7 @@ import { configDir } from "../../config/runtime/paths";
|
|
|
8
8
|
import { EngineClient } from "./engine-client";
|
|
9
9
|
import { engineHealth, type EngineHealth } from "./engine-health";
|
|
10
10
|
import { EngineProtocolError } from "../protocol/engine-protocol";
|
|
11
|
-
import {
|
|
11
|
+
import { engineLaunchEnvironment } from "./launch";
|
|
12
12
|
|
|
13
13
|
export type VerifiedEngineBinary = { path: string; sha256: string };
|
|
14
14
|
|
|
@@ -133,8 +133,7 @@ function configuredBinary(env: NodeJS.ProcessEnv): VerifiedEngineBinary | undefi
|
|
|
133
133
|
|
|
134
134
|
function launchEngine(path: string, socketPath: string, env: NodeJS.ProcessEnv): ChildProcess {
|
|
135
135
|
const child = spawn(path, [], {
|
|
136
|
-
env:
|
|
137
|
-
...(process.platform === "win32" ? { GRANTTAP_ENGINE_STATE_DIR: join(configDir(), "engine", "state") } : {}) },
|
|
136
|
+
env: engineLaunchEnvironment(env, socketPath),
|
|
138
137
|
stdio: "ignore",
|
|
139
138
|
shell: false,
|
|
140
139
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Engine process launch
|
|
2
|
+
|
|
3
|
+
`index.ts` prepares the verified Engine child's environment. Rust parser workers
|
|
4
|
+
receive a minimum 32 MiB stack reserve; larger explicit reserves remain intact.
|
|
5
|
+
This prevents the reproduced daemon abort on long Rust expression trees. It does
|
|
6
|
+
not change the parent environment, enablement, policy, or provider settings.
|
|
7
|
+
Windows keeps its private Engine state directory and local endpoint mapping.
|
|
8
|
+
|
|
9
|
+
The native regression analyzes a real synthetic Rust checkout through
|
|
10
|
+
`EngineSupervisor` and confirms Graph data plus a subsequent control ping.
|
|
11
|
+
Environment tests cover disablement, invalid and larger reserves, parent
|
|
12
|
+
immutability and Windows state placement. Set `GRANTTAP_ENGINE_TEST_BINARY` to
|
|
13
|
+
an exact verified native binary to run the native case.
|
|
14
|
+
|
|
15
|
+
This is a fixed worker reserve, not a guarantee against arbitrary resource
|
|
16
|
+
exhaustion. [Engine bridge](../../README.md) owns the public runtime boundary.
|
|
17
|
+
|
|
18
|
+
License: GrantTap Commercial Source License; see the repository-root `LICENSE`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { configDir } from "../../../config/runtime/paths";
|
|
3
|
+
import { engineSocketEndpoint } from "../../startup";
|
|
4
|
+
|
|
5
|
+
const MINIMUM_PARSER_STACK_BYTES = 32 * 1_024 * 1_024;
|
|
6
|
+
|
|
7
|
+
/** Native syntax parsing runs on Rust worker threads, outside the JS stack. */
|
|
8
|
+
export function engineLaunchEnvironment(
|
|
9
|
+
env: NodeJS.ProcessEnv, socketPath: string, platform: NodeJS.Platform = process.platform,
|
|
10
|
+
): NodeJS.ProcessEnv {
|
|
11
|
+
const requestedStack = Number(env.RUST_MIN_STACK);
|
|
12
|
+
return {
|
|
13
|
+
...env,
|
|
14
|
+
GRANTTAP_ENGINE_SOCKET: engineSocketEndpoint(socketPath),
|
|
15
|
+
// Rust's default worker stack aborts the daemon on ordinary long ASTs.
|
|
16
|
+
// Scope this reserve to the verified Engine child; keep larger explicit reserves.
|
|
17
|
+
RUST_MIN_STACK: String(Number.isSafeInteger(requestedStack)
|
|
18
|
+
? Math.max(MINIMUM_PARSER_STACK_BYTES, requestedStack) : MINIMUM_PARSER_STACK_BYTES),
|
|
19
|
+
...(platform === "win32" ? { GRANTTAP_ENGINE_STATE_DIR: join(configDir(), "engine", "state") } : {}),
|
|
20
|
+
};
|
|
21
|
+
}
|
package/cursor-plugin/README.md
CHANGED
|
@@ -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.
|
|
13
|
+
npm install -g granttap-mcp@0.8.106
|
|
14
14
|
granttap setup
|
|
15
15
|
granttap status
|
|
16
16
|
```
|
package/cursor-plugin/mcp.json
CHANGED
|
@@ -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.
|
|
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.106\";\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.
|
|
10
|
+
const PACKAGE = "granttap-mcp@0.8.106";
|
|
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
|
@@ -49,6 +49,8 @@ export const UserMessage = z.object({
|
|
|
49
49
|
agent: CodingAgent.optional(),
|
|
50
50
|
cwd: z.string().max(4_096).optional(),
|
|
51
51
|
requestId: z.string().optional(),
|
|
52
|
+
/** Explicit compatible opt-in for native question file/image replies. */
|
|
53
|
+
questionReplyFormat: z.literal("attachments-v1").optional(),
|
|
52
54
|
sessionId: z.string().optional(),
|
|
53
55
|
attachments: z.array(UserAttachment).max(MAX_USER_ATTACHMENTS).optional(),
|
|
54
56
|
/** Attachments that came ahead of this message, by id. */
|
|
@@ -122,6 +124,8 @@ export const AgentEvent = z.object({
|
|
|
122
124
|
type: z.literal("agent.event"),
|
|
123
125
|
text: z.string(),
|
|
124
126
|
requestId: z.string().optional(),
|
|
127
|
+
/** Explicit compatible opt-in for native question file/image replies. */
|
|
128
|
+
questionReplyFormat: z.literal("attachments-v1").optional(),
|
|
125
129
|
kind: z.enum(["question", "status", "response"]).optional(),
|
|
126
130
|
sessionId: z.string().optional(),
|
|
127
131
|
agent: CodingAgent.optional(),
|