granttap-mcp 0.8.75 → 0.8.78
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/LICENSE +72 -202
- package/LICENSE-APACHE-2.0.txt +202 -0
- package/NOTICE +4 -2
- package/README.md +13 -6
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/apps/bridge/src/account-recovery/README.md +1 -1
- package/apps/bridge/src/approvals/cancellation/README.md +8 -0
- package/apps/bridge/src/approvals/cancellation/index.ts +20 -0
- package/apps/bridge/src/approvals/index.ts +6 -0
- package/apps/bridge/src/bin/cli/setup.ts +5 -8
- package/apps/bridge/src/bin/hooks/cursor-hook.ts +6 -1
- package/apps/bridge/src/bin/runtime/cursor-mcp-hook.ts +6 -1
- package/apps/bridge/src/bin/runtime/monitor.ts +16 -7
- package/apps/bridge/src/capabilities/README.md +1 -1
- package/apps/bridge/src/claude-questions/README.md +1 -1
- package/apps/bridge/src/cloud-runners/README.md +1 -1
- package/apps/bridge/src/codex-desktop/README.md +31 -0
- package/apps/bridge/src/codex-desktop/connection.ts +104 -0
- package/apps/bridge/src/codex-desktop/controls.ts +21 -0
- package/apps/bridge/src/codex-desktop/index.ts +78 -0
- package/apps/bridge/src/codex-desktop/receipts.ts +55 -0
- package/apps/bridge/src/codex-desktop/socket-path.ts +25 -0
- package/apps/bridge/src/codex-hook-trust/README.md +1 -1
- package/apps/bridge/src/config/README.md +1 -1
- package/apps/bridge/src/config/runtime/index.ts +3 -2
- package/apps/bridge/src/configuration-delivery/README.md +1 -1
- package/apps/bridge/src/delivery/index.ts +14 -0
- package/apps/bridge/src/device-network/README.md +2 -2
- package/apps/bridge/src/engine/README.md +17 -4
- package/apps/bridge/src/engine/distribution/README.md +20 -0
- package/apps/bridge/src/engine/distribution/artifacts.ts +35 -0
- package/apps/bridge/src/engine/distribution/download.ts +29 -0
- package/apps/bridge/src/engine/distribution/index.ts +3 -0
- package/apps/bridge/src/engine/distribution/installation.ts +44 -0
- package/apps/bridge/src/engine/distribution/monitor/README.md +8 -0
- package/apps/bridge/src/engine/distribution/monitor/index.ts +47 -0
- package/apps/bridge/src/engine/distribution/storage.ts +31 -0
- package/apps/bridge/src/engine/distribution/windows-storage.ts +32 -0
- package/apps/bridge/src/engine/protocol/engine-declaration.ts +5 -1
- package/apps/bridge/src/engine/runtime/engine-client.ts +2 -1
- package/apps/bridge/src/engine/runtime/engine-supervisor.ts +3 -1
- package/apps/bridge/src/engine/startup/README.md +15 -0
- package/apps/bridge/src/engine/startup/activation-entry.ts +3 -0
- package/apps/bridge/src/engine/startup/activation.ts +18 -0
- package/apps/bridge/src/engine/startup/index.ts +3 -0
- package/apps/bridge/src/engine/startup/local-endpoint.ts +13 -0
- package/apps/bridge/src/engine/startup/recovery.ts +45 -0
- package/apps/bridge/src/host/approval-presentation/README.md +1 -1
- package/apps/bridge/src/host/session-key-store/README.md +1 -1
- package/apps/bridge/src/install/inspect.ts +2 -0
- package/apps/bridge/src/install/linux-service/README.md +19 -0
- package/apps/bridge/src/install/linux-service/definition.ts +34 -0
- package/apps/bridge/src/install/linux-service/index.ts +107 -0
- package/apps/bridge/src/install/monitor-helper.ts +13 -35
- package/apps/bridge/src/install/monitor-launchd/README.md +8 -0
- package/apps/bridge/src/install/monitor-launchd/index.ts +25 -0
- package/apps/bridge/src/machine-load/README.md +1 -1
- package/apps/bridge/src/machine-load/command-process/README.md +1 -1
- package/apps/bridge/src/managed-admission/README.md +1 -1
- package/apps/bridge/src/mesh/README.md +1 -1
- package/apps/bridge/src/mesh/catalog/README.md +1 -1
- package/apps/bridge/src/mesh/context/README.md +1 -1
- package/apps/bridge/src/mesh/knowledge/README.md +1 -1
- package/apps/bridge/src/mesh/repository-details/README.md +1 -1
- package/apps/bridge/src/mesh/restrictions/README.md +1 -1
- package/apps/bridge/src/mesh/runtime/graph/README.md +1 -1
- package/apps/bridge/src/monitor/README.md +1 -1
- package/apps/bridge/src/monitor/handlers/catalog.ts +7 -4
- package/apps/bridge/src/monitor/handlers/inbound.ts +6 -1
- package/apps/bridge/src/monitor/handlers/receipts.ts +4 -2
- package/apps/bridge/src/monitor/handlers/user-message.ts +19 -5
- package/apps/bridge/src/monitor/mesh-publication/README.md +1 -1
- package/apps/bridge/src/monitor/message-delivery/README.md +5 -1
- package/apps/bridge/src/monitor/message-delivery/index.ts +6 -4
- package/apps/bridge/src/monitor/support/runtime.ts +5 -1
- package/apps/bridge/src/policy/README.md +1 -1
- package/apps/bridge/src/policy/shell/README.md +1 -1
- package/apps/bridge/src/provider-account-status/README.md +1 -1
- package/apps/bridge/src/provider-model-catalog/README.md +1 -1
- package/apps/bridge/src/reply/README.md +12 -1
- package/apps/bridge/src/reply/cursor-context/README.md +18 -0
- package/apps/bridge/src/reply/cursor-context/index.ts +64 -0
- package/apps/bridge/src/reply/cursor-conversation/README.md +21 -0
- package/apps/bridge/src/reply/cursor-conversation/actions.ts +44 -0
- package/apps/bridge/src/reply/cursor-conversation/call-context/README.md +9 -0
- package/apps/bridge/src/reply/cursor-conversation/call-context/index.ts +32 -0
- package/apps/bridge/src/reply/cursor-conversation/index.ts +86 -0
- package/apps/bridge/src/reply/cursor-conversation/interactions.ts +39 -0
- package/apps/bridge/src/reply/cursor-conversation/permissions.ts +67 -0
- package/apps/bridge/src/reply/cursor-conversation/questions.ts +66 -0
- package/apps/bridge/src/reply/cursor-conversation/transport.ts +136 -0
- package/apps/bridge/src/reply/index.ts +3 -0
- package/apps/bridge/src/reply/payload/attachments.ts +29 -5
- package/apps/bridge/src/reply/payload/identity.ts +13 -0
- package/apps/bridge/src/reply/payload/types.ts +6 -2
- package/apps/bridge/src/reply/process/codex-headless.ts +8 -2
- package/apps/bridge/src/reply/process/index.ts +8 -4
- package/apps/bridge/src/reply/process/provider-headless.ts +6 -36
- package/apps/bridge/src/session-catalog/README.md +1 -1
- package/apps/bridge/src/session-images/README.md +1 -1
- package/apps/bridge/src/sessions/README.md +1 -1
- package/apps/bridge/src/sessions/cursor/README.md +1 -1
- package/apps/bridge/src/sessions/cursor/activity.ts +4 -0
- package/apps/bridge/src/sessions/cursor/attention/README.md +1 -1
- package/apps/bridge/src/sessions/cursor/catalog.ts +3 -0
- package/apps/bridge/src/sessions/cursor/conversations/README.md +13 -0
- package/apps/bridge/src/sessions/cursor/conversations/history.ts +33 -0
- package/apps/bridge/src/sessions/cursor/conversations/index.ts +80 -0
- package/apps/bridge/src/sessions/cursor/conversations/storage.ts +68 -0
- package/apps/bridge/src/sessions/cursor/executions/README.md +12 -0
- package/apps/bridge/src/sessions/cursor/executions/index.ts +46 -0
- package/apps/bridge/src/sessions/cursor/index.ts +2 -0
- package/apps/bridge/src/sessions/cursor/scan.ts +2 -1
- package/apps/bridge/src/sessions/scan/codex/README.md +1 -1
- package/apps/bridge/src/sessions/telemetry/README.md +1 -1
- package/apps/bridge/src/task-automation/README.md +1 -1
- package/apps/bridge/src/task-control/README.md +1 -1
- package/apps/bridge/src/task-control/index.ts +4 -0
- package/apps/bridge/src/task-dispatch/README.md +1 -1
- package/apps/bridge/src/task-files/README.md +1 -1
- package/apps/bridge/src/task-network/README.md +1 -1
- package/apps/bridge/src/task-network/configuration/README.md +7 -1
- package/apps/bridge/src/task-network/configuration/runtime.ts +1 -1
- package/apps/bridge/src/task-network/configuration/sync.ts +21 -4
- package/apps/bridge/src/task-origins/README.md +1 -1
- package/apps/bridge/src/task-routing/README.md +1 -1
- package/apps/bridge/src/task-routing/resources/README.md +1 -1
- package/apps/bridge/src/task-transfer/README.md +1 -1
- package/apps/bridge/src/task-transfer/continuation/README.md +1 -1
- package/apps/bridge/src/task-transfer/runtime/README.md +1 -1
- package/apps/bridge/src/transcript-history/README.md +1 -1
- package/apps/bridge/src/transcript-history/index.ts +8 -0
- package/apps/mcp/src/connection-center/README.md +1 -1
- package/apps/mcp/src/desktop/README.md +1 -1
- package/apps/mcp/src/desktop/delivery/README.md +1 -1
- package/apps/mcp/src/desktop/delivery/task-send.ts +1 -1
- package/apps/mcp/src/desktop/live-catalog/README.md +1 -1
- package/apps/mcp/src/desktop/models/README.md +1 -1
- package/apps/mcp/src/desktop/native-access/README.md +1 -1
- package/apps/mcp/src/desktop/network/README.md +1 -1
- package/apps/mcp/src/desktop/pairing/README.md +1 -1
- package/apps/mcp/src/desktop/policy/README.md +1 -1
- package/apps/mcp/src/desktop/project-transport/README.md +1 -1
- package/apps/mcp/src/desktop/provider-hooks/README.md +1 -1
- package/apps/mcp/src/desktop/provider-storage/README.md +2 -2
- package/apps/mcp/src/desktop/task-control/README.md +1 -1
- package/apps/mcp/src/desktop/task-queue/README.md +1 -1
- package/apps/mcp/src/desktop/task-transfer/README.md +1 -1
- package/apps/mcp/src/desktop/workspace/README.md +1 -1
- package/apps/mcp/src/http-service/README.md +1 -1
- package/apps/mcp/src/http-service/common.ts +2 -0
- package/apps/mcp/src/http-service/installer.ts +8 -1
- package/apps/mcp/src/http-service/snapshot.ts +9 -0
- package/apps/mcp/src/mcp-tools/README.md +1 -1
- package/apps/mcp/src/oauth/README.md +1 -1
- package/bin/granttap-mcp.mjs +3 -1
- package/cursor-plugin/.cursor-plugin/plugin.json +2 -2
- package/cursor-plugin/LICENSE +72 -202
- package/cursor-plugin/NOTICE +4 -2
- package/cursor-plugin/README.md +3 -3
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +4 -3
- package/packages/core/README.md +1 -1
- package/packages/core/ciphertext-recipient/README.md +1 -1
- package/packages/core/outbound-scheduler/README.md +1 -1
- package/packages/core/socket-lifecycle/README.md +1 -1
- package/packages/core/store-file-lock/README.md +1 -1
- package/packages/core/task-job-http/README.md +1 -1
- package/packages/protocol/messages/README.md +1 -1
- package/packages/protocol/messages/provider-hooks/README.md +1 -1
- package/packages/protocol/task-dispatch/README.md +1 -1
- package/packages/protocol/task-transfer/README.md +1 -1
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type InstallResult,
|
|
12
12
|
} from "../../install";
|
|
13
13
|
import { createOneTimePairing, DEFAULT_RELAY, PAIRING_CODE_TTL_MINUTES } from "../../pairing";
|
|
14
|
-
import {
|
|
14
|
+
import { prepareEngine } from "../../engine/distribution";
|
|
15
15
|
import { isMachineConfigured } from "../../../../mcp/src/status/pairing-status";
|
|
16
16
|
import { removeCursorUserGrantTap } from "../../../../mcp/src/cursor-config";
|
|
17
17
|
import { installHttpMcpService, waitForHttpMcpHealth } from "../../../../mcp/src/http-service";
|
|
@@ -59,15 +59,14 @@ async function main(): Promise<void> {
|
|
|
59
59
|
// Declared before the helper is written, so the LaunchAgent picks the engine
|
|
60
60
|
// up in the same run rather than needing a second one.
|
|
61
61
|
const engineFlag = process.argv.indexOf("--engine");
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
62
|
+
if (engineFlag >= 0 && !process.argv[engineFlag + 1]) throw new Error("Choose an executable after --engine");
|
|
63
|
+
const engine = await prepareEngine(engineFlag >= 0 ? process.argv[engineFlag + 1] : undefined);
|
|
65
64
|
const helper = installMonitorHelper();
|
|
66
65
|
let httpService: InstallResult | null = null;
|
|
67
66
|
let cursorConfig: InstallResult | null = null;
|
|
68
67
|
const mcpClientInstalled = before.agents.some((item) => item.installed);
|
|
69
68
|
if (before.cursor.installed || mcpClientInstalled) {
|
|
70
|
-
httpService = installHttpMcpService();
|
|
69
|
+
httpService = installHttpMcpService({ forceReload: true });
|
|
71
70
|
if (process.platform === "win32" && httpService.status !== "manual"
|
|
72
71
|
&& !await waitForHttpMcpHealth(undefined, 15_000)) {
|
|
73
72
|
httpService = { status: "manual", detail: "Windows background service did not become healthy." };
|
|
@@ -112,9 +111,7 @@ async function main(): Promise<void> {
|
|
|
112
111
|
? "Next: keep the GrantTap plugin. Pair and change settings in the GrantTap app, not Cursor MCP settings."
|
|
113
112
|
: !paired
|
|
114
113
|
? "Next: pair from the GrantTap plugin connection card or the GrantTap app."
|
|
115
|
-
:
|
|
116
|
-
? "Governance is live. Restart the helper to pick it up: launchctl kickstart -k gui/$(id -u)/com.granttap.monitor"
|
|
117
|
-
: "Governance stays off until an engine is found. Point at one with: granttap setup --engine <path>",
|
|
114
|
+
: "Ready. Continue from GrantTap on your controller device.",
|
|
118
115
|
"",
|
|
119
116
|
].join("\n"));
|
|
120
117
|
}
|
|
@@ -112,6 +112,11 @@ async function continueApproval(
|
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
if (process.env.GRANTTAP_CURSOR_ACP === "1") {
|
|
116
|
+
nativeAsk("GrantTap will show this action through the active Cursor conversation.");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
const timeoutMs = Number(process.env.GRANTTAP_APPROVAL_TIMEOUT_MS ?? 60_000);
|
|
116
121
|
let decision;
|
|
117
122
|
try {
|
|
@@ -137,6 +142,6 @@ function cursorCwd(input: CursorHookInput): string | undefined {
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
main().catch(() => {
|
|
140
|
-
|
|
145
|
+
deny("GrantTap could not verify this Cursor Task's execution scope.");
|
|
141
146
|
process.exit(0);
|
|
142
147
|
});
|
|
@@ -122,6 +122,11 @@ async function continueApproval(
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
if (process.env.GRANTTAP_CURSOR_ACP === "1") {
|
|
126
|
+
nativeAsk("GrantTap will show this action through the active Cursor conversation.");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
125
130
|
const timeoutMs = Number(process.env.GRANTTAP_APPROVAL_TIMEOUT_MS ?? 60_000);
|
|
126
131
|
let decision;
|
|
127
132
|
try {
|
|
@@ -170,6 +175,6 @@ function cursorWorkspaceRoot(input: CursorMcpHookInput): string | undefined {
|
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
main().catch(() => {
|
|
173
|
-
|
|
178
|
+
deny("GrantTap could not verify this Cursor Task's execution scope.");
|
|
174
179
|
process.exit(0);
|
|
175
180
|
});
|
|
@@ -3,8 +3,10 @@ import { join } from "node:path";
|
|
|
3
3
|
import { RelayClient } from "../../../../../packages/core/relay-client";
|
|
4
4
|
import { configDir, loadConfig, machineConfigPath } from "../../config";
|
|
5
5
|
import { EngineSupervisor } from "../../engine/runtime/engine-supervisor";
|
|
6
|
+
import { prepareMonitorEngine } from "../../engine/distribution";
|
|
7
|
+
import { startEngineRecovery } from "../../engine/startup";
|
|
6
8
|
import { meshSnapshotsAsync } from "../../mesh/runtime";
|
|
7
|
-
import { publishedCatalog
|
|
9
|
+
import { publishedCatalog } from "../../session-catalog";
|
|
8
10
|
import { recoverLocalGraphBindings } from "../../mesh/runtime/graph/binding-sync";
|
|
9
11
|
import { startSessionMonitor } from "../../monitor";
|
|
10
12
|
import { controllerPeerGate } from "../../pairing/controllers";
|
|
@@ -22,6 +24,7 @@ let stopping = false;
|
|
|
22
24
|
let stopEndpointPublisher = () => {};
|
|
23
25
|
let stopTaskDispatch = () => {};
|
|
24
26
|
let stopTaskNetwork = () => {};
|
|
27
|
+
let stopEngineRecovery = () => {};
|
|
25
28
|
|
|
26
29
|
try {
|
|
27
30
|
const cfg = applyNetworkRoute(loadConfig(machineConfigPath()));
|
|
@@ -47,6 +50,7 @@ try {
|
|
|
47
50
|
const stop = (): void => {
|
|
48
51
|
if (stopping) return;
|
|
49
52
|
stopping = true;
|
|
53
|
+
stopEngineRecovery();
|
|
50
54
|
stopTaskDispatch();
|
|
51
55
|
stopTaskNetwork();
|
|
52
56
|
monitor.close();
|
|
@@ -58,8 +62,9 @@ const stop = (): void => {
|
|
|
58
62
|
process.on("SIGINT", stop);
|
|
59
63
|
process.on("SIGTERM", stop);
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
stopEngineRecovery = startEngineRecovery({
|
|
66
|
+
prepare: prepareMonitorEngine,
|
|
67
|
+
available: async () => {
|
|
63
68
|
const health = await engine.ensureAvailable();
|
|
64
69
|
if (health.state !== "disabled" && health.state !== "healthy") {
|
|
65
70
|
process.stderr.write(`[granttap-mcp] Project engine ${health.state}; legacy behavior remains active\n`);
|
|
@@ -74,9 +79,13 @@ void recoverCatalogBindings({
|
|
|
74
79
|
throw error;
|
|
75
80
|
}
|
|
76
81
|
},
|
|
77
|
-
|
|
82
|
+
recover: async () => {
|
|
78
83
|
const known = publishedCatalog(client);
|
|
79
|
-
|
|
84
|
+
const snapshots = await meshSnapshotsAsync(known ? [...known.sessions, ...(known.history ?? [])] : []);
|
|
85
|
+
await recoverLocalGraphBindings(snapshots);
|
|
86
|
+
await monitor.publish();
|
|
80
87
|
},
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
failed: error => {
|
|
89
|
+
process.stderr.write(`[granttap-mcp] Engine recovery will retry (${error instanceof Error ? error.name : "unknown"})\n`);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
@@ -11,4 +11,4 @@ Catalog observations and Project requests do not install a skill or native MCP
|
|
|
11
11
|
configuration on another host. Availability requires a separate native apply
|
|
12
12
|
and independently observed initialization; that rollout is still unfinished.
|
|
13
13
|
|
|
14
|
-
License: this module is distributed under the
|
|
14
|
+
License: this module is distributed under the GrantTap Commercial Source License in the repository-root `LICENSE` file.
|
|
@@ -10,4 +10,4 @@ an `answers` mapping using Claude's documented PreToolUse `updatedInput` contrac
|
|
|
10
10
|
|
|
11
11
|
Tests: `tests/`. Engineering contract: the Apple repository `AGENTS.md`.
|
|
12
12
|
|
|
13
|
-
License:
|
|
13
|
+
License: GrantTap Commercial Source License; see the repository LICENSE.
|
|
@@ -45,4 +45,4 @@ The first message is the Task Capsule: goal, status, repository, branch and
|
|
|
45
45
|
commit, the person's comment, decisions and remaining work, bounded to 12,000
|
|
46
46
|
characters. The visible conversation archive is not uploaded.
|
|
47
47
|
|
|
48
|
-
Tests: [`tests/`](tests/).
|
|
48
|
+
Tests: [`tests/`](tests/). GrantTap Commercial Source License, see [LICENSE](../../../../LICENSE).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Codex desktop delivery
|
|
2
|
+
|
|
3
|
+
Public entry: `deliverToCodexDesktop` in `index.ts`. A paired controller's follow-up
|
|
4
|
+
goes to the native owner of the existing conversation, including between turns.
|
|
5
|
+
Starting a second `codex exec resume` against that owner's thread would conflict
|
|
6
|
+
with its single writer. New Tasks continue to use the provider's CLI creation path.
|
|
7
|
+
|
|
8
|
+
The local desktop coordination protocol is versioned and differs from the public
|
|
9
|
+
app-server WebSocket transport. This adapter implements its framing independently;
|
|
10
|
+
no desktop application source is bundled. The existing socket must be private,
|
|
11
|
+
owned by the current user, and inside the configured Codex home. Nothing creates,
|
|
12
|
+
replaces, or restarts the native router. Windows has no tested desktop socket path
|
|
13
|
+
and keeps the CLI path.
|
|
14
|
+
|
|
15
|
+
The native owner inherits its workspace and approval settings. Explicit per-Task
|
|
16
|
+
sandbox settings and model/effort are carried with the turn. Disabled MCP settings
|
|
17
|
+
that cannot be enforced through the owner are refused. Protocol errors or an
|
|
18
|
+
unconfirmed submission never fall back to a second writer. Only a missing socket
|
|
19
|
+
or an explicit owner-not-found response permits headless resume.
|
|
20
|
+
|
|
21
|
+
The original message ID is carried into Codex. A private, cross-process receipt
|
|
22
|
+
records a hash of the intent before dispatch and native turn ID after acceptance.
|
|
23
|
+
An uncertain or crashed dispatch is not replayed. A native receipt produces no
|
|
24
|
+
synthetic assistant reply or completed-run digest; the existing transcript carries
|
|
25
|
+
the actual work. Pause sends the user's stop request to the same owner. Document
|
|
26
|
+
attachments remain in private staging for up to a day after admission; the monitor
|
|
27
|
+
removes expired stages. Image-only stages are removed once native admission has
|
|
28
|
+
consumed them. Tests exercise native delivery, framing, socket trust, durable
|
|
29
|
+
deduplication, and refusal without launching the CLI.
|
|
30
|
+
|
|
31
|
+
License: GrantTap Commercial Source License in the repository-root `LICENSE`.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { connect, type Socket } from 'node:net';
|
|
3
|
+
|
|
4
|
+
type Response = { type: 'response'; requestId: string; method?: string; resultType: string;
|
|
5
|
+
handledByClientId?: string; result?: any; error?: string };
|
|
6
|
+
type Pending = { method: string; target?: string; resolve: (response: Response) => void;
|
|
7
|
+
reject: (error: Error) => void; timer: NodeJS.Timeout };
|
|
8
|
+
const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
/** The desktop coordination socket is length-prefixed JSON, not another app-server. */
|
|
11
|
+
export class DesktopConnection {
|
|
12
|
+
private readonly socket: Socket;
|
|
13
|
+
private readonly pending = new Map<string, Pending>();
|
|
14
|
+
private buffered = Buffer.alloc(0);
|
|
15
|
+
private clientId = 'initializing-client';
|
|
16
|
+
private closed = false;
|
|
17
|
+
private readonly deadline: number;
|
|
18
|
+
|
|
19
|
+
constructor(path: string, timeoutMs: number) {
|
|
20
|
+
this.deadline = Date.now() + Math.min(5_000, timeoutMs);
|
|
21
|
+
this.socket = connect(path);
|
|
22
|
+
this.socket.on('error', () => this.close());
|
|
23
|
+
this.socket.on('close', () => this.close());
|
|
24
|
+
this.socket.on('data', chunk => {
|
|
25
|
+
try { this.receive(chunk); } catch { this.close(); }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize(): Promise<void> {
|
|
30
|
+
const response = await this.call('initialize', { clientType: 'desktop' }, 0);
|
|
31
|
+
const id = response.result?.clientId;
|
|
32
|
+
if (response.resultType !== 'success' || typeof id !== 'string' || !id || id.length > 180) {
|
|
33
|
+
throw new Error('Codex desktop initialization is unavailable.');
|
|
34
|
+
}
|
|
35
|
+
this.clientId = id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
call(method: string, params: unknown, version: number, target?: string): Promise<Response> {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
if (this.closed) return reject(new Error('Codex desktop disconnected.'));
|
|
41
|
+
const requestId = randomUUID();
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
this.pending.delete(requestId);
|
|
44
|
+
reject(new Error('Codex desktop has not confirmed the request.'));
|
|
45
|
+
}, Math.max(1, this.deadline - Date.now()));
|
|
46
|
+
this.pending.set(requestId, { method, target, resolve, reject, timer });
|
|
47
|
+
this.send({ type: 'request', requestId, sourceClientId: this.clientId,
|
|
48
|
+
method, params, version, ...(target ? { targetClientId: target } : {}) });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
close(): void {
|
|
53
|
+
if (this.closed) return;
|
|
54
|
+
this.closed = true;
|
|
55
|
+
this.socket.destroy();
|
|
56
|
+
this.buffered = Buffer.alloc(0);
|
|
57
|
+
for (const request of this.pending.values()) {
|
|
58
|
+
clearTimeout(request.timer);
|
|
59
|
+
request.reject(new Error('Codex desktop disconnected.'));
|
|
60
|
+
}
|
|
61
|
+
this.pending.clear();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private send(value: unknown): void {
|
|
65
|
+
const body = Buffer.from(JSON.stringify(value));
|
|
66
|
+
if (body.length > MAX_FRAME_BYTES) return this.close();
|
|
67
|
+
const prefix = Buffer.alloc(4);
|
|
68
|
+
prefix.writeUInt32LE(body.length);
|
|
69
|
+
this.socket.write(Buffer.concat([prefix, body]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private receive(chunk: Buffer): void {
|
|
73
|
+
this.buffered = Buffer.concat([this.buffered, chunk]);
|
|
74
|
+
while (this.buffered.length >= 4) {
|
|
75
|
+
const size = this.buffered.readUInt32LE();
|
|
76
|
+
if (size === 0 || size > MAX_FRAME_BYTES) throw new Error('Invalid desktop frame.');
|
|
77
|
+
if (this.buffered.length < size + 4) return;
|
|
78
|
+
const message = JSON.parse(this.buffered.subarray(4, size + 4).toString());
|
|
79
|
+
this.buffered = this.buffered.subarray(size + 4);
|
|
80
|
+
this.handle(message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private handle(message: any): void {
|
|
85
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) return;
|
|
86
|
+
if (message.type === 'client-discovery-request') {
|
|
87
|
+
this.send({ type: 'client-discovery-response', requestId: message.requestId,
|
|
88
|
+
response: { canHandle: false } });
|
|
89
|
+
} else if (message.type === 'request') {
|
|
90
|
+
this.send({ type: 'response', requestId: message.requestId,
|
|
91
|
+
resultType: 'error', error: 'no-handler-for-request' });
|
|
92
|
+
} else if (message.type === 'response') {
|
|
93
|
+
const request = this.pending.get(message.requestId);
|
|
94
|
+
if (!request) return;
|
|
95
|
+
if (message.method !== undefined && message.method !== request.method
|
|
96
|
+
|| message.resultType === 'success' && request.target && message.handledByClientId !== request.target) {
|
|
97
|
+
return this.close();
|
|
98
|
+
}
|
|
99
|
+
this.pending.delete(message.requestId);
|
|
100
|
+
clearTimeout(request.timer);
|
|
101
|
+
request.resolve(message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DesktopConnection } from './connection';
|
|
2
|
+
import { desktopSocketPath } from './socket-path';
|
|
3
|
+
|
|
4
|
+
/** A deliberate user stop reaches the owning desktop; no provider process is killed. */
|
|
5
|
+
export async function interruptCodexDesktop(sessionId: string): Promise<{ ok: boolean } | undefined> {
|
|
6
|
+
let connection: DesktopConnection | undefined;
|
|
7
|
+
try {
|
|
8
|
+
const path = desktopSocketPath();
|
|
9
|
+
if (!path) return undefined;
|
|
10
|
+
connection = new DesktopConnection(path, 2_500);
|
|
11
|
+
await connection.initialize();
|
|
12
|
+
const owner = await connection.call('thread-owner-discovery', { hostId: 'local', conversationId: sessionId }, 1);
|
|
13
|
+
if (owner.resultType === 'error' && owner.error === 'no-client-found') return undefined;
|
|
14
|
+
if (owner.resultType !== 'success' || !owner.handledByClientId) return { ok: false };
|
|
15
|
+
// Version 3 is the native explicit-user-stop form, without a stale expected turn ID.
|
|
16
|
+
const response = await connection.call('thread-follower-interrupt-turn',
|
|
17
|
+
{ conversationId: sessionId, mode: 'user-stop', expectedTurnId: null }, 3, owner.handledByClientId);
|
|
18
|
+
return { ok: response.resultType === 'success' && response.result?.ok === true };
|
|
19
|
+
} catch { return { ok: false }; }
|
|
20
|
+
finally { connection?.close(); }
|
|
21
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import type { SessionInfo } from '../../../../packages/protocol/schema';
|
|
4
|
+
import { loadRuntimeConfig } from '../config';
|
|
5
|
+
import type { DeliveryOptions, ReplyResult } from '../reply';
|
|
6
|
+
import { DesktopConnection } from './connection';
|
|
7
|
+
import { DesktopReceipt } from './receipts';
|
|
8
|
+
import { desktopSocketPath } from './socket-path';
|
|
9
|
+
export { interruptCodexDesktop } from './controls';
|
|
10
|
+
|
|
11
|
+
const UNAVAILABLE = 'Codex desktop is not ready to receive this message. Keep the original chat open and try again.';
|
|
12
|
+
const UNCERTAIN = 'Codex has not confirmed this message. Check the original chat before sending it again.';
|
|
13
|
+
|
|
14
|
+
/** Undefined means no desktop owner exists; only then may the caller resume a CLI. */
|
|
15
|
+
export async function deliverToCodexDesktop(input: {
|
|
16
|
+
session: SessionInfo; text: string; images: string[]; options: DeliveryOptions; timeoutMs: number;
|
|
17
|
+
}): Promise<ReplyResult | undefined> {
|
|
18
|
+
let connection: DesktopConnection | undefined;
|
|
19
|
+
let dispatched = false;
|
|
20
|
+
const { session, text, images, options } = input;
|
|
21
|
+
const accepted: ReplyResult = { ok: true, text: '', sessionId: session.sessionId, delivery: 'native' };
|
|
22
|
+
try {
|
|
23
|
+
const policy = loadRuntimeConfig();
|
|
24
|
+
const request = turnRequest(input, policy.sessionAccess[session.sessionId]);
|
|
25
|
+
const intent = options.intentDigest ?? { text,
|
|
26
|
+
images: images.map(path => createHash('sha256').update(readFileSync(path)).digest('hex')), options };
|
|
27
|
+
const receipt = new DesktopReceipt(session.sessionId, request.clientUserMessageId, intent);
|
|
28
|
+
const prior = receipt.lookup();
|
|
29
|
+
if (prior === 'accepted') return accepted;
|
|
30
|
+
if (prior) return refusal(prior);
|
|
31
|
+
const path = desktopSocketPath();
|
|
32
|
+
if (!path) return undefined;
|
|
33
|
+
connection = new DesktopConnection(path, input.timeoutMs);
|
|
34
|
+
await connection.initialize();
|
|
35
|
+
const owner = await connection.call('thread-owner-discovery',
|
|
36
|
+
{ hostId: 'local', conversationId: session.sessionId }, 1);
|
|
37
|
+
if (owner.resultType === 'error' && owner.error === 'no-client-found') return undefined;
|
|
38
|
+
if (owner.resultType !== 'success' || !owner.handledByClientId) return { ok: false, error: UNAVAILABLE };
|
|
39
|
+
if (policy.sessionMcpDisabled[session.sessionId]?.length) {
|
|
40
|
+
return { ok: false, error: 'Apply this Task’s disabled MCP settings in Codex before continuing its open desktop chat.' };
|
|
41
|
+
}
|
|
42
|
+
const state = receipt.begin();
|
|
43
|
+
if (state === 'accepted') return accepted;
|
|
44
|
+
if (state !== 'new') return refusal(state);
|
|
45
|
+
dispatched = true;
|
|
46
|
+
const response = await connection.call('thread-follower-start-turn', {
|
|
47
|
+
conversationId: session.sessionId,
|
|
48
|
+
turnStart: { request, context: { inheritThreadSettings: true } },
|
|
49
|
+
}, 2, owner.handledByClientId);
|
|
50
|
+
const turnId = response.result?.result?.turn?.id;
|
|
51
|
+
if (response.resultType !== 'success' || typeof turnId !== 'string' || !turnId || turnId.length > 180) {
|
|
52
|
+
return { ok: false, error: UNCERTAIN, delivery: 'native' };
|
|
53
|
+
}
|
|
54
|
+
receipt.accept(turnId);
|
|
55
|
+
return accepted;
|
|
56
|
+
} catch {
|
|
57
|
+
return { ok: false, error: dispatched ? UNCERTAIN : UNAVAILABLE,
|
|
58
|
+
...(dispatched ? { delivery: 'native' as const } : {}) };
|
|
59
|
+
} finally { connection?.close(); }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function refusal(state: string): ReplyResult {
|
|
63
|
+
return state === 'conflict' ? { ok: false, error: 'This message ID belongs to a different Codex delivery.' }
|
|
64
|
+
: { ok: false, error: UNCERTAIN, delivery: 'native' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function turnRequest(input: { session: SessionInfo; text: string; images: string[]; options: DeliveryOptions }, access?: string) {
|
|
68
|
+
const { session, text, images, options } = input;
|
|
69
|
+
const effort = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(options.effort ?? '') ? options.effort : undefined;
|
|
70
|
+
const sandboxPolicy = access === 'read-only' ? { type: 'readOnly', networkAccess: false }
|
|
71
|
+
: access === 'workspace' ? { type: 'workspaceWrite', writableRoots: session.cwd ? [session.cwd] : [],
|
|
72
|
+
networkAccess: false, excludeTmpdirEnvVar: false, excludeSlashTmp: false }
|
|
73
|
+
: access === 'full' ? { type: 'dangerFullAccess' } : undefined;
|
|
74
|
+
return { threadId: session.sessionId, clientUserMessageId: options.messageId ?? randomUUID(),
|
|
75
|
+
input: [{ type: 'text', text, text_elements: [] }, ...images.map(path => ({ type: 'localImage', path }))],
|
|
76
|
+
...(options.model ? { model: options.model } : {}), ...(effort ? { effort } : {}),
|
|
77
|
+
...(sandboxPolicy ? { sandboxPolicy } : {}) };
|
|
78
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { closeSync, existsSync, fsyncSync, openSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { withStoreLock } from '../../../../packages/core/store-file-lock';
|
|
6
|
+
import { configDir, writePrivateFile } from '../config';
|
|
7
|
+
|
|
8
|
+
const Receipt = z.object({ digest: z.string().length(64), phase: z.enum(['sending', 'accepted']),
|
|
9
|
+
turnId: z.string().min(1).max(180).optional() }).strict();
|
|
10
|
+
type Record = z.infer<typeof Receipt>;
|
|
11
|
+
const hash = (value: string) => createHash('sha256').update(value).digest('hex');
|
|
12
|
+
|
|
13
|
+
/** A crashed or disconnected submission is never replayed as a new native turn. */
|
|
14
|
+
export class DesktopReceipt {
|
|
15
|
+
private readonly path: string;
|
|
16
|
+
private readonly digest: string;
|
|
17
|
+
|
|
18
|
+
constructor(threadId: string, messageId: string, intent: unknown) {
|
|
19
|
+
this.path = join(configDir(), 'codex-desktop-deliveries', hash(`${threadId}:${messageId}`) + '.json');
|
|
20
|
+
this.digest = hash(JSON.stringify(intent));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
begin(): 'new' | 'accepted' | 'uncertain' | 'conflict' {
|
|
24
|
+
return withStoreLock(this.path, () => {
|
|
25
|
+
const prior = this.lookup();
|
|
26
|
+
if (prior) return prior;
|
|
27
|
+
this.save({ digest: this.digest, phase: 'sending' });
|
|
28
|
+
return 'new';
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
lookup(): 'accepted' | 'uncertain' | 'conflict' | undefined {
|
|
33
|
+
if (!existsSync(this.path)) return undefined;
|
|
34
|
+
const prior = this.read();
|
|
35
|
+
if (prior.digest !== this.digest) return 'conflict';
|
|
36
|
+
return prior.phase === 'accepted' ? 'accepted' : 'uncertain';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
accept(turnId: string): void {
|
|
40
|
+
withStoreLock(this.path, () => {
|
|
41
|
+
const prior = this.read();
|
|
42
|
+
if (prior.digest !== this.digest) throw new Error('Codex delivery identity changed.');
|
|
43
|
+
this.save({ ...prior, phase: 'accepted', turnId });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private read(): Record { return Receipt.parse(JSON.parse(readFileSync(this.path, 'utf8'))); }
|
|
48
|
+
private save(record: Record): void {
|
|
49
|
+
writePrivateFile(this.path, JSON.stringify(record) + '\n');
|
|
50
|
+
for (const path of [this.path, ...(process.platform === 'win32' ? [] : [dirname(this.path)])]) {
|
|
51
|
+
const fd = openSync(path, 'r');
|
|
52
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { lstatSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/** Only the user's existing desktop router may receive a paired controller's input. */
|
|
6
|
+
export function desktopSocketPath(): string | undefined {
|
|
7
|
+
if (process.platform === 'win32') return undefined;
|
|
8
|
+
const root = process.env.GRANTTAP_CODEX_DIR ?? process.env.NODVOX_CODEX_DIR
|
|
9
|
+
?? process.env.CODEX_HOME ?? join(homedir(), '.codex');
|
|
10
|
+
const directory = join(root, 'ipc');
|
|
11
|
+
const socket = join(directory, 'ipc.sock');
|
|
12
|
+
for (const [path, isSocket] of [[root, false], [directory, false], [socket, true]] as const) {
|
|
13
|
+
try {
|
|
14
|
+
const stat = lstatSync(path);
|
|
15
|
+
if (stat.uid !== process.getuid?.() || (stat.mode & 0o022) !== 0
|
|
16
|
+
|| (isSocket ? !stat.isSocket() || (stat.mode & 0o077) !== 0 : !stat.isDirectory())) {
|
|
17
|
+
throw new Error('The Codex desktop connection is not private.');
|
|
18
|
+
}
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return socket;
|
|
25
|
+
}
|
|
@@ -6,5 +6,5 @@ and device pairing serialization. `../config.ts` is its public entry point.
|
|
|
6
6
|
Behavior is covered by `tests/config.test.ts`, `tests/capabilities.test.ts`, and
|
|
7
7
|
`tests/core.test.ts`.
|
|
8
8
|
|
|
9
|
-
License: this module is distributed under the
|
|
9
|
+
License: this module is distributed under the GrantTap Commercial Source License
|
|
10
10
|
in the repository-root `LICENSE` file.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { isAbsolute, join, win32 } from "node:path";
|
|
3
3
|
import { writePrivateFile } from "../access/write-private";
|
|
4
4
|
import type {
|
|
5
5
|
AgentAccess,
|
|
@@ -132,7 +132,8 @@ function parseSessionList(raw: unknown): string[] {
|
|
|
132
132
|
|
|
133
133
|
/** Only an absolute path can be verified, so a relative one is not kept. */
|
|
134
134
|
function parseEnginePath(raw: unknown): string | null {
|
|
135
|
-
return typeof raw === "string" && raw.
|
|
135
|
+
return typeof raw === "string" && raw.length <= 1024 && !/^[/\\]{2}/.test(raw)
|
|
136
|
+
&& (isAbsolute(raw) || win32.isAbsolute(raw)) ? raw : null;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
function parseEngineChecksum(raw: unknown): string | null {
|
|
@@ -7,4 +7,4 @@ computer-enforced chat level. Socket delivery alone is never an applied rule.
|
|
|
7
7
|
The configuration and task-automation modules own durable admission and tests.
|
|
8
8
|
Engineering contract: the Apple repository `AGENTS.md`.
|
|
9
9
|
|
|
10
|
-
License:
|
|
10
|
+
License: GrantTap Commercial Source License; see the repository LICENSE.
|
|
@@ -12,6 +12,7 @@ type DeliveryRecord = {
|
|
|
12
12
|
state: "processing" | "completed";
|
|
13
13
|
updatedAt: number;
|
|
14
14
|
processingAcknowledged?: boolean;
|
|
15
|
+
rejectedError?: string;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export type DeliveryStart = "started" | "processing" | "completed";
|
|
@@ -89,6 +90,19 @@ export function completeDelivery(messageId: string, now = Date.now()): void {
|
|
|
89
90
|
save(records, now);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/** A late provider refusal remains terminal across reconnects and replayed boxes. */
|
|
94
|
+
export function rejectCompletedDelivery(messageId: string, error: string, now = Date.now()): void {
|
|
95
|
+
const records = load();
|
|
96
|
+
records[messageId] = { state: 'completed', updatedAt: now, rejectedError: error.slice(0, 1_000) };
|
|
97
|
+
save(records, now);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function completedDeliveryFailure(messageId: string): string | undefined {
|
|
101
|
+
const record = load()[messageId];
|
|
102
|
+
return record?.state === 'completed' && typeof record.rejectedError === 'string'
|
|
103
|
+
? record.rejectedError : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
92
106
|
/** Release a failed attempt so a relay retry can safely try again. */
|
|
93
107
|
export function abandonDelivery(messageId: string, now = Date.now()): void {
|
|
94
108
|
const records = load();
|
|
@@ -12,7 +12,7 @@ forwarding or promise background iPhone delivery. Self-hosted mode has no
|
|
|
12
12
|
managed directory announcement. Settings never contain pairing secret keys.
|
|
13
13
|
|
|
14
14
|
Behavior tests live in `tests/network.test.ts`. This machine runtime module is
|
|
15
|
-
covered by the repository
|
|
15
|
+
covered by the repository GrantTap Commercial Source License; the separately installed relay follows
|
|
16
16
|
its own license and customer terms.
|
|
17
17
|
|
|
18
|
-
This runtime module is covered by the repository
|
|
18
|
+
This runtime module is covered by the repository GrantTap Commercial Source License.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This module owns the versioned local IPC boundary between the public machine
|
|
4
4
|
runtime and the separately distributed GrantTap Engine. `engine-client.ts`
|
|
5
|
-
implements bounded length-prefixed framing and a persistent multiplexed
|
|
6
|
-
socket. A timed-out request is never retried automatically
|
|
7
|
-
path. `engine-supervisor.ts`
|
|
5
|
+
implements bounded length-prefixed framing and a persistent multiplexed local
|
|
6
|
+
socket or Windows named pipe. A timed-out request is never retried automatically
|
|
7
|
+
on the hook-critical path. `engine-supervisor.ts` verifies the configured
|
|
8
8
|
binary by SHA-256 before launch, and reports explicit health without changing
|
|
9
9
|
legacy hook behavior when the engine is disabled or unavailable.
|
|
10
10
|
`engine-projects.ts` forwards only bounded local Project/binding records when
|
|
@@ -49,5 +49,18 @@ the durable journal.
|
|
|
49
49
|
|
|
50
50
|
The proprietary engine source and Project database do not live in this package.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Every CLI entry point loads the persisted Engine declaration before importing
|
|
53
|
+
its runtime. The monitor, HTTP MCP and provider hooks therefore share activation
|
|
54
|
+
on macOS, Linux and Windows rather than relying on a LaunchAgent environment.
|
|
55
|
+
Explicit disablement and a complete explicit executable/checksum pair win over
|
|
56
|
+
the saved declaration. Partial environment overrides are never mixed with a
|
|
57
|
+
different file's checksum. The supervisor still verifies the executable's bytes
|
|
58
|
+
before launch. Windows clients derive a local named pipe from the normalized
|
|
59
|
+
configuration path; remote shares never become Engine endpoints.
|
|
60
|
+
The paired monitor installs the pinned managed Engine in the background when
|
|
61
|
+
needed. Catalog delivery does not await that download. Failed preparation retries
|
|
62
|
+
without losing phone questions, and a restarted helper re-admits verified local
|
|
63
|
+
checkouts. Explicit disablement and custom declarations are preserved.
|
|
64
|
+
|
|
65
|
+
License: this module is distributed under the GrantTap Commercial Source License
|
|
53
66
|
in the repository-root `LICENSE` file.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Engine distribution
|
|
2
|
+
|
|
3
|
+
`index.ts` exposes installation of the commercial Engine. Release
|
|
4
|
+
metadata is reviewed with the MCP source and pins the exact platform, size and
|
|
5
|
+
SHA-256. Downloads use the fixed GrantTap HTTPS origin, no redirects or cookies,
|
|
6
|
+
a deadline and a streaming byte limit. A download cannot choose its own hash.
|
|
7
|
+
|
|
8
|
+
Verified executables live in versioned private directories. New bytes are
|
|
9
|
+
written and synced to an exclusive temporary file before an atomic rename.
|
|
10
|
+
Failed downloads leave the previous declared Engine untouched. Setup and the
|
|
11
|
+
paired monitor save a new declaration only after verification.
|
|
12
|
+
Custom executable declarations remain available through `setup --engine`.
|
|
13
|
+
|
|
14
|
+
Provider hooks never download an executable. The catalog can connect and replay
|
|
15
|
+
questions independently of Engine installation and repository analysis.
|
|
16
|
+
The monitor prepares a missing or outdated managed Engine in the background,
|
|
17
|
+
retries failed downloads and recovers verified bindings once it becomes healthy.
|
|
18
|
+
Explicit disablement and custom executable declarations remain in force.
|
|
19
|
+
|
|
20
|
+
License: GrantTap Commercial Source License, in the repository-root `LICENSE`.
|